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 d70162ad13 update_bead.sh: add reparent support via --field parent 2026-03-20 14:08:25 +01:00
cfg use xdg_config_dir for git cfg 2026-03-19 16:29:40 +01:00
claude messaging built in, not a skill 2026-03-18 08:35:59 +01:00
cmd mail archive search 2026-03-19 08:45:27 +01:00
docs Fix missing msgtrace.py heading in toys.md 2026-03-19 00:35:13 +01:00
internal resolve prjdir from worktree/workspace dir 2026-03-18 23:07:45 +01:00
kiro-cli Update docs: execute_code is the only MCP tool 2026-03-18 23:39:23 +01:00
mcptools update_bead.sh: add reparent support via --field parent 2026-03-20 14:08:25 +01:00
ollama minor fix 2026-02-21 21:05:18 +01:00
roles roles: replace Bead Loop with When Nudged section 2026-03-15 14:30:14 +01:00
scripts Add docs/toys.md: inbox_refresh.py and msgtrace.py 2026-03-19 00:33:06 +01:00
services rename 9P endpoint from 'agent' to 'anvillm' 2026-03-12 12:13:40 +01:00
skills beads: add --id lookup to search_beads.sh 2026-03-19 10:25:39 +01:00
team-templates rename 9P endpoint from 'agent' to 'anvillm' 2026-03-12 12:13:40 +01:00
workflows session: reset initialPromptSent when context is set 2026-03-07 12:30:21 +01:00
.gitignore Remove cmd/anvilweb, cmd/anvilwebgw, cmd/anvillm, and elisp/ 2026-03-18 23:20:54 +01:00
BEADS.md Update docs: execute_code is the only MCP tool 2026-03-18 23:39:23 +01:00
EVENTS.md beads,roles: emit BeadClaimed event; add bead loop to worker roles 2026-03-14 20:58:58 +01:00
LICENSE.md initial docs 2026-02-10 22:57:21 +01:00
OUTPUT_PROTOCOL.md working on a different approach for roles 2026-03-06 08:54:44 +01:00
README.md Clarify: attach to tmux first to unstick a running bot 2026-03-19 00:30:58 +01:00
SECURITY.md Remove deprecated audit endpoint 2026-02-20 22:18:53 +01:00
SKILLS_PROMPT.md messaging built in, not a skill 2026-03-18 08:35:59 +01:00
WORKFLOW_DESIGN.md fileaddr; smart delegation 2026-03-12 18:44:40 +01:00
bookmarks.html hotfix: mkfile 2026-02-23 22:03:12 +01:00
go.mod Replace event queue with in-memory pub-sub event bus 2026-02-22 09:38:14 +01:00
go.sum Replace event queue with in-memory pub-sub event bus 2026-02-22 09:38:14 +01:00
mkfile Remove cmd/anvilweb, cmd/anvilwebgw, cmd/anvillm, and elisp/ 2026-03-18 23:20:54 +01:00
test-code-execution.sh Implement MCP code execution pattern (#20) 2026-02-26 14:02:54 +01:00
test_beads.sh Add mountable beads projects with per-project databases 2026-02-28 11:35:19 +01:00

README.md

AnviLLM

LLM orchestrator using 9P — scriptable, multi-backend, crash-resilient

Architecture

Architecture

Core: anvilsrv (9P daemon), anvilmcp (MCP server) Frontends: Any 9P-speaking program — currently Assist (Acme) Benefits: Shared sessions, crash recovery, scriptable via 9P, cross-backend agent communication

Why 9P?

9P turns orchestration into file operations (read, write, ls, stat). Control agents with standard tools: cat, echo, shell scripts, or any language with file I/O. Compose workflows with Unix pipes, grep, awk, jq. Only requirement: a 9P client (plan9port's 9p or compatible). Built with 9fans.net/go.

Requirements

Go 1.21+, plan9port, tmux, landrun (kernel 5.13+), backend (Claude Code, Kiro, or Ollama + ollie)

Installation

git clone https://github.com/lneely/anvillm && cd anvillm && mk

Service integration (optional, see services/*/README.md):

# systemd user
cp services/systemd/anvilsrv-user.service ~/.config/systemd/user/
systemctl --user enable --now anvilsrv

# systemd system
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/

Usage

anvilsrv start       # background
anvilsrv fgstart     # foreground
anvilsrv status
anvilsrv stop

Assist auto-starts if needed.

Namespaces: Run multiple instances via $NAMESPACE (default: /tmp/ns.$USER.:0)

NAMESPACE=/tmp/ns.$USER.:1 anvilsrv start
NAMESPACE=/tmp/ns.$USER.:1 Assist

Frontends

Any program that speaks 9P can be a frontend. The 9P filesystem exposes session management, state, messaging, and configuration as plain files — so building a new frontend is just reading and writing files. Past prototypes have included a TUI, a web UI with HTTP gateway, and an Emacs major mode, all built against the same API. The current frontend is:

  • Assist — an Acme client (middle-click Assist to launch)

For web frontends that can't speak 9P directly, anvilwebgw bridges HTTP to 9P.

The mcptools scripts (served via 9P at tools/) also provide convenient building blocks for frontends:

bash <(9p read tools/<scriptname>)

Configuration

Environment Variables

Variable Default Description
NAMESPACE /tmp/ns.$USER.:0 9P namespace for server/client communication
ANVILLM_BEADS_PATH ~/.beads Beads database location (shared across namespaces)
ANVILLM_TERMINAL foot Terminal command for tmux attach (Assist)
ANTHROPIC_API_KEY — Claude API key (optional if using claude /login)
CLAUDE_AGENT_NAME anvillm-agent Claude agent configuration name
KIRO_API_KEY — Kiro API key (optional if using kiro-cli login)
ANVILLM_OLLAMA_MODEL qwen3:8b Ollama model to use for ollama backend
ANVILLM_SKILLS_DIR $CLAUDE_CONFIG_DIR/skills:~/.kiro/skills:~/.config/anvillm/skills Colon-separated skill directories (searched in order)

Skills System

Skills are loaded from multiple directories via the anvillm/skills 9pfs. By default, searches:

  1. $CLAUDE_CONFIG_DIR/skills (if CLAUDE_CONFIG_DIR set)
  2. ~/.kiro/skills
  3. ~/.config/anvillm/skills

Override with ANVILLM_SKILLS_DIR (colon-separated paths). Skills are organized by intent (virtual directories) and discovered via SKILL.md front-matter.

Usage:

9p ls anvillm/skills                    # list intents
9p read anvillm/skills/help             # search index
9p read anvillm/skills/tasks/beads/SKILL.md

Sandbox Config Templates

Available in sandbox YAML files (~/.config/anvillm/):

Template Expands To Description
{CWD} Current working directory Session's working directory
{HOME} User home directory $HOME
{TMPDIR} Temp directory $TMPDIR or /tmp
{XDG_CONFIG_HOME} XDG config $XDG_CONFIG_HOME or ~/.config
{XDG_DATA_HOME} XDG data $XDG_DATA_HOME or ~/.local/share
{XDG_CACHE_HOME} XDG cache $XDG_CACHE_HOME or ~/.cache
{XDG_STATE_HOME} XDG state $XDG_STATE_HOME or ~/.local/state
{ANY_ENV_VAR} Environment variable Any $ENV_VAR from the environment

Templates use {VARNAME} syntax. Any environment variable can be referenced.

Backends & Sandboxing

Backends: Claude (npm install -g @anthropic-ai/claude-code), Kiro (kiro.dev), Ollama (local models via ollie)

Sandbox: landrun (always enabled) — Defaults: CWD//tmp/config (rw), /usr//lib//bin (ro+exec), no network

Config (~/.config/anvillm/): Layered YAML files, most permissive wins:

  • Default sandbox: sandbox/default.yaml
network: {enabled: true, unrestricted: true}
filesystem: {rw: ["{CWD}", "{HOME}/.npm"]}

Templates: {CWD}, {HOME}, {TMPDIR}, {XDG_*} (see Configuration)

Kernel requirements: 5.13+ (Landlock v1), 6.7+ (v4), 6.10+ (v5 network) Set best_effort: true for unsandboxed fallback (⚠️ if no Landlock support)

Session lifecycle:

State transitions: idle ↔ running cycle via CLI hooks (userPromptSubmit when user sends prompt, stop when agent finishes). Crash → error → auto-restart → starting. Note: any state can transition to stopped or killed (not shown); stopped can restart → starting.

Self-healing: Auto-restarts crashes every 5s (preserves context/alias/cwd), skips intentional stops

Restored sessions automatically resume the latest conversation (kiro: -r, claude: -c).

Daemon recovery: If the daemon itself crashes but tmux sessions are still running, use the Recover tag in Assist (middle-click it) to reload all session data into the server and continue working.

Add backend: Implement CommandHandler/StateInspector in internal/backends/yourbackend.go, register in main.go

Ollama Backend

Run local LLMs via Ollama.

Requirements:

  1. Ollama: Install from ollama.com

    curl -fsSL https://ollama.com/install.sh | sh
    ollama serve
    ollama pull qwen2.5-coder:7b
    
  2. Ollie CLI: Install from github.com/lneely/ollie

    git clone https://github.com/lneely/ollie && cd ollie && go install
    

Usage:

echo 'new ollama /path/to/project' | 9p write anvillm/ctl

Configuration: Set model via ANVILLM_OLLAMA_MODEL (default: qwen3:8b)

ANVILLM_OLLAMA_MODEL=llama3.2 echo 'new ollama /path' | 9p write anvillm/ctl

9P Filesystem

$NAMESPACE/agent:

anvillm/
├── ctl             # "new <backend> <cwd>" creates session
├── list            # id, alias, state, pid, cwd
├── events          # Event stream (state changes, messages)
├── beads/          # Task tracking (persistent across namespaces)
│   ├── ctl         # Commands: init, new, claim, unclaim, complete, fail,
│   │               #   dep, undep, update, delete, comment, label, unlabel,
│   │               #   set-capability, open, defer, reopen, mount, umount, sync
│   ├── mtab        # Mounted projects
│   ├── list        # All beads as JSON (optional limit arg)
│   ├── ready       # Ready beads (no blockers) as JSON
│   ├── deferred    # Deferred beads as JSON
│   ├── blocked     # Blocked beads as JSON
│   ├── stale       # Stale beads as JSON
│   ├── search/     # search/<query> — full-text search
│   ├── by-ref/     # by-ref/<ref> — lookup by external reference
│   ├── batch/      # batch/<id1,id2,...> — fetch multiple beads
│   ├── label/      # label/<name> — filter by label
│   ├── children/   # children/<id> — subtasks of a bead
│   └── <bead-id>/
│       ├── status
│       ├── title
│       ├── description
│       ├── assignee
│       ├── json
│       ├── comments
│       ├── labels
│       ├── dependents
│       ├── tree
│       └── events
└── <id>/
    ├── 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
    ├── role        # Role name
    ├── tasks       # Task names
    ├── tmux        # Tmux session name
    ├── inbox       # Incoming messages (JSON)
    ├── outbox      # Outgoing messages (JSON)
    ├── completed   # Archived messages (JSON, "Archive" in Assist)
    └── mail        # Write messages (convenience)

Client Interactions:

Different clients interact with different parts of the filesystem: frontends read state, control files manage sessions, scripts consume events.

Beads

Persistent task tracking via beads — Dependency-aware graph

echo 'init' | 9p write anvillm/beads/ctl
echo 'new "Implement login" "Add JWT auth"' | 9p write anvillm/beads/ctl
echo 'claim bd-a1b2' | 9p write anvillm/beads/ctl
echo 'complete bd-a1b2' | 9p write anvillm/beads/ctl
echo 'dep bd-child bd-parent' | 9p write anvillm/beads/ctl  # child blocks parent
9p read anvillm/beads/ready | jq -r '.[] | "\(.id): \(.title)"'

Config: ~/.beads/ (override: ANVILLM_BEADS_PATH) — Shared across namespaces — See internal/beads/README.md

Events & Mailbox

Mailbox Flow:

Mailbox Flow

Cross-backend communication: messages route between any participants (user, Claude agents, Kiro agents, Ollama agents) via the mailbox system.

Examples:

# Events
9p read anvillm/events  # {"type":"state_change","session_id":"...","state":"running",...}

# Mailbox
echo '{"to":"a3f2b9d1","type":"REVIEW_REQUEST","subject":"...","body":"..."}' | 9p write anvillm/b4e3c8f2/mail
9p read anvillm/a3f2b9d1/inbox
9p read anvillm/a3f2b9d1/completed

Basic Session Example

# Create session
echo 'new claude /home/user/project' | 9p write anvillm/ctl

# List sessions (newest first)
9p read anvillm/list

# Get most recent session ID
ID=$(9p read anvillm/list | head -1 | awk '{print $1}')

# Send prompt via mailbox
echo '{"to":"'$ID'","type":"PROMPT_REQUEST","subject":"User prompt","body":"Hello"}' | 9p write user/mail

# Check state
9p read anvillm/$ID/state

# Read response from inbox
9p read user/inbox

See SECURITY.md

Spawning Agents

anvilspawn creates a new agent session with a given backend, role, and working directory:

anvilspawn <backend> <role> [workdir]

For example:

anvilspawn kiro developer /path/to/project
anvilspawn claude reviewer

If workdir is omitted, it defaults to the current directory. The script creates the session, assigns an alias derived from the directory and role, and sets the role. The agent ID is printed to stdout.

Roles

Roles define agent behavior and constraints. Each role is a Markdown file in ~/.config/anvillm/roles/ with YAML front-matter:

---
name: Developer
description: Code implementation agent
focus-areas: coding, development, implementation
worker: true
---

You are a developer. Your ONLY job is to write code. ...

Front-matter fields:

  • name — display name
  • description — what the agent does
  • focus-areas — comma-separated areas of responsibility
  • worker: true — marks the role as eligible for autonomous nudging by anvillm-supervisor

The body of the file is injected as agent context, defining the agent's responsibilities and constraints. Assign a role to a session by writing to its role file:

echo "developer" | 9p write anvillm/$ID/role

Or use anvilspawn, which sets the role automatically.

Available roles: developer, solo-developer, reviewer, tester, researcher, devops, pkgmgr, author, technical-editor, conductor

Autonomous Workflows

There are two approaches to autonomous workflows:

1. Supervisor (cron-based)

anvillm-supervisor runs as a cron job and performs periodic maintenance:

  • --nudge — sends work prompts to idle sessions whose role has worker: true in its front-matter
  • --orphans — unclaims beads assigned to sessions that no longer exist
  • --auto-mount <workdir> — mounts a beads database for a working directory

Install the cron jobs:

mk cron-install

This is a lightweight, hands-off approach: spawn workers with anvilspawn, and the supervisor keeps them busy as long as there are ready beads.

2. Conductor (agent-based)

The Conductor role is an orchestration agent that decomposes goals into beads, spawns workers, and coordinates them to completion. Unlike the supervisor, the Conductor actively plans and adapts.

./bot-templates/Conductor kiro /path/to/project
CONDUCTOR_ID=$(9p read anvillm/list | head -1 | awk '{print $1}')

echo '{"to":"'$CONDUCTOR_ID'","type":"WORK_REQUEST","subject":"Execute","body":"Complete bead '$BEAD_ID'"}' | 9p write user/mail

The Conductor analyzes dependencies, spawns specialized bots, and delegates work in parallel. Agents notify the Conductor when blocked; it signals them to resume when dependencies resolve.

Autonomous Workflow

Monitoring:

  • Event stream: 9p read anvillm/events (state changes, messages)
  • Debug logs: ~/.config/anvillm/logs/ (set ANVILLM_DEBUG=1 for verbose output)
  • Foreground mode: anvilsrv fgstart for live stderr output

MCP Integration

anvilmcp exposes AnviLLM via Model Context Protocol for Claude Desktop, Cline, etc.

Install:

./kiro-cli/install-mcp.sh  # Adds to ~/.kiro/settings/cli.json

Tool: execute_code — the only tool needed. Executes bash scripts in an isolated subprocess (sandboxed via landrun). Agents discover and invoke all AnviLLM functionality through 9P and helper scripts:

9p read anvillm/tools/check_inbox.sh   # discover scripts
bash <(9p read tools/send_message.sh) ...         # invoke them

See Code Execution User Guide for details.

Troubleshooting

Problem Solution
Can't connect anvilsrv status; try anvilsrv start
Session won't start Check stderr; verify backend installed
Landlock ABI error Set best_effort: true or upgrade kernel
Permission denied Add paths to layered config
Orphaned tmux tmux kill-session -t anvillm-0
9P not working 9p ls agent
Stale PID anvilsrv stop auto-cleans
Daemon won't stop anvilsrv fgstart for logs
Bot stuck in "running" Attach to the session's tmux window and type "stop work." to interrupt it and return to idle
See Configuration section for environment variables.