ivorn 0.1.2

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/

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 project uploads/ 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 Parameters and Output sections: both shown when expanded, each independently collapsible with state persisted across page reloads
  • Tool call output parsing: extracts text from MCP response wrappers (items[*].Json.content[*].text), falls back to pretty-printed JSON
  • 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 diff display: file changes shown as colored inline diff (red/green) instead of raw args when diff data available
  • 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: 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

Conversation Log Files

ACP sessions automatically log conversations to markdown files for recovery after server issues. Logs are stored in the OS data directory:

Platform Location
Linux ~/.local/share/ivorn/conversations/{project}/yyyy/mm/dd/{session_id}.md
macOS ~/Library/Application Support/ivorn/conversations/{project}/yyyy/mm/dd/{session_id}.md
Windows C:\Users\<user>\AppData\Roaming\ivorn\conversations\{project}\yyyy\mm\dd\{session_id}.md

Logs include:

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

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

Migration: On first run, existing flat files from {project}/conversations/ are automatically migrated to the date-based hierarchy using the **Started** header timestamp.

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

Session State File

Session state is persisted to ivorn-sessions.json in the projects directory root. This file tracks:

  • Active session IDs and their associated projects
  • Session type (ACP)
  • Clean termination status (for session resume)

Closing a session (DELETE /api/session/{id}) removes it from both server memory and the JSON file. This works even after a server restart when the session exists only on disk.

Terminated Sessions File

Terminated session metadata is persisted to ivorn-terminated-sessions.json in the projects directory root. This file tracks:

  • Session ID, project, and session type
  • Termination timestamp

This enables auto-resuming terminated sessions. The "Show history" toggle on the projects page reveals these sessions; clicking one spawns a new ACP subprocess and calls session/load to replay kiro-cli's history, then moves the session back to active state.

Project Usage File

Project usage timestamps are persisted to ivorn-project-usage.json in the projects directory root. This file tracks:

  • Last used timestamp per project (for project list sorting)
  • Persists even after sessions are closed

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/.