AnviLLM was an experimental 9P interface for common CLI-based AI agents. It is no longer actively maintained, but kept for progeny.
This repository has been archived on 2026-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
Go to file
Levi Neely a856da7a34 Remove Prompt command, use only Send via outbox 2026-02-17 14:52:08 +01:00
cfg Fix 9p-read-inbox to strip .json extension when marking messages complete 2026-02-17 14:37:46 +01:00
claude Fix 9p-read-inbox to strip .json extension when marking messages complete 2026-02-17 14:37:46 +01:00
cmd Remove Prompt command, use only Send via outbox 2026-02-17 14:52:08 +01:00
elisp Fix 9p-read-inbox to strip .json extension when marking messages complete 2026-02-17 14:37:46 +01:00
internal Test passed: messages received in order 2026-02-17 14:47:25 +01:00
kiro-cli/agent Reduce idle window from 30 to 15 seconds before prompting agents 2026-02-17 14:40:21 +01:00
scripts Fix 9p-read-inbox to strip .json extension when marking messages complete 2026-02-17 14:37:46 +01:00
services feat: split monolith into anvilsrv daemon and Assist client 2026-02-14 11:30:57 +01:00
skills Fix 9p-read-inbox to strip .json extension when marking messages complete 2026-02-17 14:37:46 +01:00
workflows Remove reserved flag and fix state transitions 2026-02-16 23:07:35 +01:00
.gitignore Fix 9p-read-inbox to strip .json extension when marking messages complete 2026-02-17 14:37:46 +01:00
IDEAS.md Update IDEAS.md 2026-02-17 10:32:01 +01:00
LICENSE.md initial docs 2026-02-10 22:57:21 +01:00
README.md Rename peer-bot skill to anvillm-communication 2026-02-16 15:35:46 +01:00
SECURITY.md feat: split monolith into anvilsrv daemon and Assist client 2026-02-14 11:30:57 +01:00
go.mod feat: add curses-based TUI (anvillm) 2026-02-14 12:01:02 +01:00
go.sum feat: add curses-based TUI (anvillm) 2026-02-14 12:01:02 +01:00
main.go Implement layered sandbox configuration system 2026-02-15 18:57:55 +01:00
mkfile Fix 9p-read-inbox to strip .json extension when marking messages complete 2026-02-17 14:37:46 +01:00

README.md

AnviLLM

9P-based LLM orchestrator. Front-ends for Acme, Emacs, TUI, and web.

Architecture

AnviLLM consists of:

  • anvilsrv: Background daemon managing sessions via 9P
  • Assist: Acme UI client (auto-starts daemon if needed)
  • anvillm.el: For our emacs friends ;)
  • anvillm: Terminal UI (curses-based) for everyone else
  • anvilweb: Web interface for browser-based access

This separation allows:

  • Multiple clients to connect to the same sessions
  • Server survives client crashes/restarts
  • Scriptable workflows via 9P without UI dependencies
  • Service management (systemd/runit integration)
  • Choice of interface: Acme, terminal, Emacs, or web browser

Requirements

Installation

git clone https://github.com/lneely/anvillm
cd anvillm
mk  # installs anvilsrv and Assist to $HOME/bin

Service Integration

Install as system service (optional):

systemd:

# User service (recommended)
cp services/systemd/anvilsrv-user.service ~/.config/systemd/user/
systemctl --user enable --now anvilsrv

# System service
sudo cp services/systemd/anvilsrv.service /etc/systemd/system/
sudo systemctl enable --now anvilsrv

runit:

sudo cp -r services/runit /etc/sv/anvilsrv
sudo ln -s /etc/sv/anvilsrv /var/service/

See services/*/README.md for details.

Usage

Starting the Server

Automatic: Assist auto-starts anvilsrv if not running.

Manual:

anvilsrv start       # Daemonize (background)
anvilsrv fgstart     # Foreground (for debugging)
anvilsrv status      # Check if running
anvilsrv stop        # Shutdown

Multiple Namespaces

anvilsrv respects the $NAMESPACE environment variable, allowing multiple independent instances for different Acme sessions (similar to running multiple Acme instances with acme -a).

Default namespace: /tmp/ns.$USER.:0

Example: Running multiple instances

# Start default instance (namespace :0)
anvilsrv start

# Start second instance (namespace :1)
NAMESPACE=/tmp/ns.$USER.:1 anvilsrv start

# Check status of each instance
anvilsrv status                           # :0
NAMESPACE=/tmp/ns.$USER.:1 anvilsrv status  # :1

# Each instance has isolated:
# - PID file: $NAMESPACE/anvilsrv.pid
# - 9P socket: $NAMESPACE/agent
# - Session list (completely independent)

# Connect Assist to specific instance
NAMESPACE=/tmp/ns.$USER.:1 Assist

This enables running separate AnviLLM environments for different projects or Acme workspaces.

Terminal UI (anvillm)

Run anvillm for a curses-based interface (no Acme required):

anvillm

Keyboard shortcuts:

  • s - Start new session (shows backend menu)
  • p - Send prompt to selected session
  • t - Stop selected session
  • R - Restart selected session
  • K - Kill selected session
  • a - Set session alias
  • r - Refresh session list
  • d - Daemon status
  • ? - Help
  • q - Quit

To attach to the anvillm session, simply open a different terminal and use tmux attach.

Navigation:

  • Arrow keys (↑/↓)
  • Vim-style (j/k)
  • Emacs-style (C-n/C-p)

Like Assist, the TUI reads/writes the 9P filesystem for all operations.

Emacs Interface (anvillm.el)

Run M-x anvillm in Emacs for a native Emacs interface:

Installation:

Copy anvillm.el to ~/.emacs.d/lisp/ or wherever your elisp files live. Then, add to your emacs init:

(add-to-list 'load-path "/path/to/anvillm.el/")
(require 'anvillm)

Keybindings:

  • s - Start new session (select backend)
  • p - Send prompt to selected session
  • P - Send prompt to selected session using minibuffer
  • t - Stop selected session
  • R - Restart selected session
  • K - Kill selected session
  • a - Set alias for selected session
  • r - Refresh session list
  • g - Refresh session list (standard Emacs binding)
  • d - Daemon status
  • q - Quit
  • ? - Help

Navigation:

  • Standard Emacs navigation (n, p, C-n, C-p)
  • Sessions displayed in a sortable table (click column headers)

The Emacs interface uses the 9p command from plan9port to interact with the filesystem.

Web Interface (anvilweb)

Run anvilweb for a browser-based interface:

anvilweb              # starts on :8080
anvilweb -addr :3000  # custom port

Features:

  • Start new sessions (Kiro/Claude)
  • View all sessions with live state updates
  • Send prompts, edit context and aliases
  • Stop/restart/kill sessions
  • Auto-refreshes every 5 seconds

Security Warning:

⚠️ The web interface has NO authentication. Anyone who can reach the server port can:

  • View all sessions
  • Send prompts to any session
  • Start/stop/kill sessions
  • Modify session contexts

Only run anvilweb on localhost or trusted networks. For remote access, use SSH port forwarding:

# On remote machine
anvilweb -addr localhost:8080

# On local machine
ssh -L 8080:localhost:8080 user@remote
# Then browse to http://localhost:8080

Or use a reverse proxy with authentication (nginx, caddy, etc.).

Acme Interface (Assist)

Type Assist in Acme and middle-click to open the /AnviLLM/ session manager.

Commands

Main window (/AnviLLM/) tag: Get Attach Stop Restart Kill Alias Context Log Daemon Sandbox

Command Description
Kiro <dir> Start kiro-cli session
Claude <dir> Start Claude session
Stop <id> Stop session (preserves tmux)
Restart <id> Restart stopped session
Kill <id> Terminate session
Attach <id> Open tmux terminal
Alias <id> <name> Name a session
Context <id> Edit context (prepended to prompts)
Log <id> View session log
Daemon Manage anvilsrv daemon (start/stop/status)
Sandbox Configure sandbox settings

Right-click a session ID to open its prompt window. Select text anywhere and 2-1 chord on a session ID for fire-and-forget prompts.

Note: Assist reconnects to the running anvilsrv daemon, so sessions persist across Assist restarts.

Prompt window (+Prompt.<id>) tag: Send

Type prompt, click Send.

9P Filesystem

The anvilsrv daemon exposes sessions via a 9P filesystem at $NAMESPACE/agent (typically /tmp/ns.$USER/agent).

agent/
├── ctl             # "new <backend> <cwd>" creates session
├── list            # id, alias, state, pid, cwd
└── <id>/
    ├── in          # Write prompts
    ├── out         # Read responses
    ├── ctl         # "stop", "restart", "kill"
    ├── state       # starting, idle, running, stopped, error, exited
    ├── context     # Prepended to prompts (r/w)
    ├── alias       # Session name (r/w)
    ├── pid         # Process ID
    ├── cwd         # Working directory
    └── backend     # Backend name

Path Validation: Session creation validates and cleans paths (e.g., /../../../etc → /etc), and rejects nonexistent directories.

Example:

echo 'new claude /home/user/project' | 9p write agent/ctl
echo 'Hello' | 9p write agent/a3f2b9d1/in
9p read agent/a3f2b9d1/state  # → "running"
echo 'stop' | 9p write agent/a3f2b9d1/ctl
9p read agent/a3f2b9d1/state  # → "stopped"

Security: See SECURITY.md for 9P socket authentication limitations.

Backends

Backend Install
Claude npm install -g @anthropic-ai/claude-code
Kiro Kiro CLI

Both run with full permissions inside the sandbox (--dangerously-skip-permissions, --trust-all-tools).

Adding Backends

Create internal/backends/yourbackend.go, implement CommandHandler and StateInspector interfaces, register in main.go. See existing backends for examples.

Self-Healing

The anvilsrv daemon monitors all sessions every 5 seconds and automatically restarts sessions that crash unexpectedly. This ensures long-running workflows remain resilient to backend failures.

Behavior:

  • Detects when a session process terminates without explicit Stop command
  • Automatically restarts the session in the same working directory
  • Preserves session context and alias
  • Rate-limited to prevent restart loops (minimum 5 seconds between attempts)
  • Does not restart sessions stopped intentionally via Stop command

Sessions that crash are transparently restarted without user intervention. Check logs with anvilsrv fgstart to see auto-restart activity.

Sandboxing

Backends run inside landrun sandboxes. Sandboxing cannot be disabled — it's the only safety layer.

Defaults

  • Filesystem: CWD, /tmp, config dirs (rw); /usr, /lib, /bin (ro+exec)
  • Network: disabled
  • Mode: strict (best_effort: false) — sessions fail if sandbox unavailable

Configuration

Click Sandbox in main window, then Edit to modify ~/.config/anvillm/sandbox.yaml.

Path templates: {CWD}, {HOME}, {TMPDIR}

# Enable network
network:
  enabled: true
  unrestricted: true

# Add paths
filesystem:
  rw:
    - "{CWD}"
    - "{HOME}/.npm"

Changes apply to new sessions only.

Kernel Requirements

Kernel Landlock ABI Features
5.13+ v1 Filesystem
6.7+ v4 Improved FS
6.10+ v5 Network

Best-Effort Mode

Set best_effort: true for graceful degradation on older kernels.

Warning: If landrun is missing or kernel lacks Landlock, sessions run completely unsandboxed. Only enable if you accept this risk.

Reproducible Workflows

The 9P filesystem enables scripted multi-agent workflows. Create sessions, set contexts, and wire agents together programmatically.

Example: Developer-Reviewer

Two agents collaborate on code changes — one implements, one reviews:

./scripts/DevReview claude /path/to/project

Creates paired sessions with contexts that instruct agents to:

  1. Developer implements and stages changes
  2. Developer sends review request via agent/{reviewer-id}/in
  3. Reviewer examines diff, sends feedback or "LGTM"
  4. Loop until approved

Example: Planning

Three-agent workflow for documentation tasks:

./scripts/Planning kiro /path/to/docs
  • Research: Queries codebase and knowledge base
  • Engineering: Writes/updates documentation, queries research when needed
  • Tech-editor: Reviews for quality, requests changes from engineering

Writing Your Own

Key patterns from the example scripts:

# Create session, capture ID
echo "new claude /project" | 9p write agent/ctl
ID=$(9p read agent/list | tail -1 | awk '{print $1}')

# Set context (defines agent behavior)
cat <<EOF | 9p write agent/$ID/context
You are a code reviewer. When you receive code...
EOF

# Agents communicate via mailboxes
cat > /tmp/msg.json <<'EOF'
{"to":"$PEER_ID","type":"REVIEW_REQUEST","subject":"Review","body":"Please review staged changes"}
EOF
9p write agent/$ID/outbox/msg-$(date +%s).json < /tmp/msg.json

See workflows/DevReview and workflows/Planning for complete examples.

Troubleshooting

Problem Solution
"Failed to connect to anvilsrv" Check anvilsrv status; try anvilsrv start
Session won't start Check stderr; verify which claude or which kiro-cli
Landlock ABI error Set best_effort: true or upgrade kernel
Permission denied Add paths to sandbox config
Orphaned tmux tmux kill-session -t anvillm-claude
9P not working Verify plan9port: 9p ls agent
Stale PID file anvilsrv stop cleans up automatically
Daemon won't stop Check logs with anvilsrv fgstart
anvilweb can't connect Ensure anvilsrv is running; check namespace matches
anvilweb port in use Use -addr :PORT to specify different port

Debugging: Run anvilsrv fgstart to see daemon logs in foreground.

Terminal: Attach command uses $ANVILLM_TERMINAL or defaults to foot. Configure in environment or see cmd/Assist/main.go.