diff --git a/EVENTS.md b/EVENTS.md index fa47d0e..26126cf 100644 --- a/EVENTS.md +++ b/EVENTS.md @@ -1,6 +1,12 @@ # Events System -The events system provides a real-time text stream of all AnviLLM activity. +The events system provides a real-time text stream of all AnviLLM activity using [github.com/simonfxr/pubsub](https://github.com/simonfxr/pubsub) as the event bus. + +## Architecture + +

+ +Multiple clients can read from `agent/events` simultaneously, each consuming the same event stream for different purposes: notifications (desktop, SMS, WhatsApp), logging/auditing, custom integrations, etc. ## Reading Events diff --git a/README.md b/README.md index 9b62ce5..73724ca 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ # AnviLLM -9P-based LLM orchestrator — Front-ends: Acme, Emacs, TUI, web +LLM orchestrator using 9P — scriptable, multi-backend, crash-resilient ## Architecture -**Core:** anvilsrv (9P daemon), anvilmcp (MCP server) -**Clients:** Assist (Acme), anvillm.el (Emacs), anvillm (TUI), anvilweb (web) -**Benefits:** Shared sessions, crash-resilient, scriptable via 9P, service integration, MCP support, **cross-backend agent communication** +![Architecture](docs/diagrams/architecture.svg?v=2) + +**Core:** anvilsrv (9P daemon), anvilmcp (MCP server), anvilwebgw (API proxy) +**Clients:** Assist (Acme), anvillm.el (Emacs), anvillm (TUI), anvilweb (web) +**Benefits:** Shared sessions, crash recovery, scriptable via 9P, cross-backend agent communication ## Why 9P? -AnviLLM uses 9P because orchestration becomes file manipulation (`read`, `write`, `ls`, `stat`). Benefits: simplicity, universal access (shell, C, Go, Python, `cat`, `echo`), and composability (Unix pipes, `grep`, `awk`, `jq`). The only requirement is a 9P client (plan9port's `9p` or compatible). +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](https://9fans.net/go). ## Requirements @@ -29,10 +31,12 @@ 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 +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/ +sudo cp -r services/runit /etc/sv/anvilsrv +sudo ln -s /etc/sv/anvilsrv /var/service/ ``` ## Usage @@ -48,8 +52,8 @@ anvilsrv stop **Namespaces:** Run multiple instances via `$NAMESPACE` (default: `/tmp/ns.$USER.:0`) ```sh -NAMESPACE=/tmp/ns.$USER.:1 anvilsrv start # :1 -NAMESPACE=/tmp/ns.$USER.:1 Assist # connect +NAMESPACE=/tmp/ns.$USER.:1 anvilsrv start +NAMESPACE=/tmp/ns.$USER.:1 Assist ``` ### Clients @@ -60,7 +64,11 @@ NAMESPACE=/tmp/ns.$USER.:1 Assist # connect **Web:** `anvilweb` (port :8080) — ⚠️ NO auth, localhost only — Remote: `ssh -L 8080:localhost:8080 user@remote` -**Acme:** Type `Assist` and middle-click — Tag: `Get Attach Stop Restart Kill Alias Context Daemon Inbox Archive` — Right-click ID for prompt window, 2-1 chord for fire-and-forget +**Acme:** Type `Assist` and middle-click + +**Tag commands:** `Get Attach Stop Restart Kill Alias Context Daemon Inbox Archive` + +**Interaction:** Right-click ID for prompt window, 2-1 chord for fire-and-forget | Command | Action | |---------|--------| @@ -102,22 +110,54 @@ Available in sandbox YAML files (`~/.config/anvillm/`): | `{XDG_STATE_HOME}` | XDG state | `$XDG_STATE_HOME` or `~/.local/state` | | `{ANY_ENV_VAR}` | Environment variable | Any `$ENV_VAR` from the environment | -**Note:** Templates use `{VARNAME}` syntax. Any environment variable can be referenced. +Templates use `{VARNAME}` syntax. Any environment variable can be referenced. ## Backends & Sandboxing -**Backends:** Claude (`npm install -g @anthropic-ai/claude-code`), Kiro ([kiro.dev](https://kiro.dev)), Ollama (local models via [ollie](https://github.com/lneely/ollie)) — Sandboxed with network access +**Backends:** Claude (`npm install -g @anthropic-ai/claude-code`), Kiro ([kiro.dev](https://kiro.dev)), Ollama (local models via [ollie](https://github.com/lneely/ollie)) + +**Sandbox:** [landrun](https://github.com/zouuup/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 role: `roles/default.yaml` + +```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`). + +**Add backend:** Implement `CommandHandler`/`StateInspector` in `internal/backends/yourbackend.go`, register in `main.go` ### Ollama Backend -Run local LLMs via Ollama: +Run local LLMs via Ollama. **Requirements:** 1. **Ollama:** Install from [ollama.com](https://ollama.com) ```sh curl -fsSL https://ollama.com/install.sh | sh - ollama serve # Start server (default: localhost:11434) - ollama pull qwen2.5-coder:7b # Download a model + ollama serve + ollama pull qwen2.5-coder:7b ``` 2. **Ollie CLI:** Install from [github.com/lneely/ollie](https://github.com/lneely/ollie) @@ -130,28 +170,11 @@ Run local LLMs via Ollama: echo 'new ollama /path/to/project' | 9p write agent/ctl ``` -**Configuration:** Set model via environment variable (default: `qwen3:8b`) +**Configuration:** Set model via `ANVILLM_OLLAMA_MODEL` (default: `qwen3:8b`) ```sh ANVILLM_OLLAMA_MODEL=llama3.2 echo 'new ollama /path' | 9p write agent/ctl ``` -**Add:** Implement `CommandHandler`/`StateInspector` in `internal/backends/yourbackend.go`, register in `main.go` - -**Sandbox:** [landrun](https://github.com/zouuup/landrun) (cannot disable) — Defaults: CWD/`/tmp`/config (rw), `/usr`/`/lib`/`/bin` (ro+exec), no network - -**Config** (`~/.config/anvillm/`): `global.yaml` → `backends/.yaml` → `roles/.yaml` (default: `roles/default.yaml`) → `tasks/.yaml` — Most permissive wins - -```yaml -network: {enabled: true, unrestricted: true} -filesystem: {rw: ["{CWD}", "{HOME}/.npm"]} -``` - -Templates: `{CWD}`, `{HOME}`, `{TMPDIR}`, `{XDG_*}` (see Configuration) — Tip: Make `roles/default.yaml` permissive - -**Kernel:** 5.13+ (Landlock v1), 6.7+ (v4), 6.10+ (v5 network) — Best-effort: `best_effort: true` (⚠️ unsandboxed if no Landlock) - -**Self-healing:** Auto-restarts crashes every 5s (preserves context/alias/cwd) — Skips intentional stops - ## 9P Filesystem `$NAMESPACE/agent`: @@ -188,6 +211,12 @@ agent/ └── 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](https://github.com/steveyegge/beads) — Dependency-aware graph @@ -205,6 +234,14 @@ Config: `~/.beads/` (override: `ANVILLM_BEADS_PATH`) — Shared across namespace ### Events & Mailbox +**Mailbox Flow:** + +![Mailbox Flow](docs/diagrams/mailbox-flow.svg?v=2) + +Cross-backend communication: messages route between any participants (user, Claude agents, Kiro agents, Ollama agents) via the mailbox system. + +**Examples:** + ```sh # Events 9p read agent/events # {"type":"state_change","session_id":"...","state":"running",...} @@ -223,7 +260,6 @@ echo 'new claude /home/user/project' | 9p write agent/ctl # List sessions (newest first) 9p read agent/list -# Output: # Get most recent session ID ID=$(9p read agent/list | head -1 | awk '{print $1}') @@ -232,41 +268,43 @@ ID=$(9p read agent/list | head -1 | awk '{print $1}') echo '{"to":"'$ID'","type":"PROMPT_REQUEST","subject":"User prompt","body":"Hello"}' | 9p write user/mail # Check state -9p read agent/$ID/state # "running" or "idle" +9p read agent/$ID/state # Read response from inbox -9p read user/inbox # Lists message files +9p read user/inbox ``` See `SECURITY.md` -## Workflows +## Bot Templates -**Multi-Agent Communication:** Agents can communicate with each other **regardless of backend** (Claude, Kiro, etc.) via the built-in mailbox system. The `anvillm-communication` skill provides a convenient interface for agent messaging, enabling heterogeneous multi-agent workflows where different specialists use different backends while collaborating seamlessly. +Bot templates provide pre-configured agents for common tasks. Templates are shell scripts that create sessions with specialized context and configuration. -**Skills:** `anvillm-skills list`, `anvillm-skills load anvillm-communication` - -**Examples:** `./bot-templates/DevReview claude /path` (dev/reviewer loop), `./bot-templates/Planning kiro /path` (research/eng/editor) - -**Pattern:** +**Available Templates:** +- `Taskmaster` — Converts project plans into dependency-aware task graphs (beads) +- `Conductor` — Orchestrates parallel execution by spawning and coordinating specialized agents +**Usage:** ```sh -echo "new claude /project" | 9p write agent/ctl -ID=$(9p read agent/list | tail -1 | awk '{print $1}') -echo "You are..." | 9p write agent/$ID/context -echo '{"to":"...","type":"...","subject":"...","body":"..."}' | 9p write agent/$ID/mail -echo 'new "Task"' | 9p write agent/beads/ctl +./bot-templates/Taskmaster kiro /path/to/project +./bot-templates/Conductor kiro /path/to/project ``` -See `bot-templates/`, `kiro-cli/SKILLS_PROMPT.md` +Templates set up context, roles, and initial configuration. Interact via any client (Assist, TUI, Emacs, Web) or directly via 9P mailbox. -## Automating Development +**Creating Templates:** Shell scripts that write to `agent/ctl` and configure `context`, `role`, `tasks`. See existing templates for examples. -Automate development workflows with Taskmaster and Conductor bots. Input a project plan → Taskmaster creates tasks with dependencies → Conductor orchestrates parallel execution. +## Autonomous Workflows + +Automate workflows with Taskmaster and Conductor bots. Input project plan → Taskmaster creates tasks with dependencies → Conductor orchestrates parallel execution. + +![Autonomous Workflow](docs/diagrams/automation-workflow.svg?v=3) + +Conductor receives the top-level bead ID, analyzes dependencies, and spawns agents to work in parallel. Agents notify Conductor when blocked; Conductor signals them to resume when dependencies resolve. ### Usage -Examples below use raw 9P calls. These actions can also be performed from any front-end (Assist, TUI, Emacs, Web). +Examples use raw 9P calls. These actions can also be performed from any front-end (Assist, TUI, Emacs, Web). **1. Taskmaster: Create tasks from project plan** @@ -291,13 +329,13 @@ CONDUCTOR_ID=$(9p read agent/list | head -1 | awk '{print $1}') echo '{"to":"'$CONDUCTOR_ID'","type":"WORK_REQUEST","subject":"Execute","body":"Complete bead '$BEAD_ID'"}' | 9p write user/mail ``` -Conductor analyzes dependencies, spawns specialized bots (DevReview, Research, Docs), and delegates work in parallel. +Conductor analyzes dependencies, spawns specialized bots, and delegates work in parallel. **Monitoring:** Use any front-end (Assist, TUI, Emacs, Web). For custom integrations, see `EVENTS.md`. ## MCP Integration -`anvilmcp` exposes AnviLLM via Model Context Protocol for Claude Desktop, Cline, etc +`anvilmcp` exposes AnviLLM via Model Context Protocol for Claude Desktop, Cline, etc. **Install:** ```sh @@ -306,7 +344,7 @@ Conductor analyzes dependencies, spawns specialized bots (DevReview, Research, D **Tools:** `read_inbox`, `send_message`, `list_sessions`, `set_state` -Enables LLM clients to control AnviLLM sessions and communicate with agents +Enables LLM clients to discover agents and communicate via the mailbox system. ## Troubleshooting diff --git a/bot-templates/DevReview b/bot-templates/DevReview deleted file mode 100755 index 366c74d..0000000 --- a/bot-templates/DevReview +++ /dev/null @@ -1,122 +0,0 @@ -#!/bin/bash -# DevReview - Developer-Reviewer Workflow -# Usage: ./scripts/DevReview - -set -e - -if [ $# -ne 2 ]; then - echo "Usage: $0 " - echo " backend: claude, kiro-cli" - exit 1 -fi - -BACKEND="$1" -PROJECT_PATH="$2" -PROJECT_NAME=$(basename "$PROJECT_PATH") - -# Generate a random short identifier -RANDOM_WORD=$(shuf -n1 -e alpha beta gamma delta zeta theta kappa sigma omega lambda) -RANDOM_NUM=$(printf "%02d" $((RANDOM % 100))) -ALIAS_NAME="${RANDOM_WORD}${RANDOM_NUM}" - -echo "Setting up Developer-Reviewer workflow for: $PROJECT_NAME" -echo "Project path: $PROJECT_PATH" -echo "Session alias: $ALIAS_NAME" - -NAMESPACE=$(namespace) -if [ -z "$NAMESPACE" ]; then - echo "Error: No namespace found. Is anvillm running?" - exit 1 -fi - -AGENT_MOUNT="agent" - -echo "Creating developer session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $PROJECT_PATH role=developer tasks=git,editor,go,rust,python,nodejs,docker,podman,maven,gradle,plan9,gpg,github-cli,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -DEV_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) -echo "Developer session ID: $DEV_ID" - -echo "Creating reviewer session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $PROJECT_PATH role=reviewer tasks=git,github-cli,plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -REVIEWER_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) -echo "Reviewer session ID: $REVIEWER_ID" - -# Set aliases -echo "Setting aliases..." -echo "${ALIAS_NAME}-dev" | 9p write $AGENT_MOUNT/$DEV_ID/alias -echo "${ALIAS_NAME}-reviewer" | 9p write $AGENT_MOUNT/$REVIEWER_ID/alias - -# Set developer context -cat < - -set -e - -if [ $# -ne 2 ]; then - echo "Usage: $0 " - echo " backend: claude, kiro-cli" - exit 1 -fi - -BACKEND="$1" -PROJECT_PATH="$2" -PROJECT_NAME=$(basename "$PROJECT_PATH") - -# Generate a random short identifier -RANDOM_WORD=$(shuf -n1 -e alpha beta gamma delta zeta theta kappa sigma omega lambda) -RANDOM_NUM=$(printf "%02d" $((RANDOM % 100))) -ALIAS_NAME="${RANDOM_WORD}${RANDOM_NUM}" - -echo "Setting up Developer-Reviewer workflow for: $PROJECT_NAME" -echo "Project path: $PROJECT_PATH" -echo "Session alias: $ALIAS_NAME" - -NAMESPACE=$(namespace) -if [ -z "$NAMESPACE" ]; then - echo "Error: No namespace found. Is anvillm running?" - exit 1 -fi - -AGENT_MOUNT="agent" - -echo "Creating developer session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $PROJECT_PATH role=developer tasks=git,editor,go,rust,python,nodejs,docker,podman,maven,gradle,plan9,gpg,github-cli,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -DEV_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) -echo "Developer session ID: $DEV_ID" - -echo "Creating reviewer session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $PROJECT_PATH role=reviewer tasks=git,github-cli,plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -REVIEWER_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) -echo "Reviewer session ID: $REVIEWER_ID" - -echo "Creating QA session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $PROJECT_PATH role=tester tasks=git,github-cli,plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -QA_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) -echo "QA session ID: $QA_ID" - -# Set aliases -echo "Setting aliases..." -echo "${ALIAS_NAME}-dev" | 9p write $AGENT_MOUNT/$DEV_ID/alias -echo "${ALIAS_NAME}-reviewer" | 9p write $AGENT_MOUNT/$REVIEWER_ID/alias -echo "${ALIAS_NAME}-qa" | 9p write $AGENT_MOUNT/$QA_ID/alias - -# Set developer context -cat < - -set -e - -if [ $# -ne 2 ]; then - echo "Usage: $0 " - echo " backend: claude, kiro-cli" - exit 1 -fi - -BACKEND="$1" -DOC_PATH="$2" - -RANDOM_WORD=$(shuf -n1 -e alpha beta gamma delta zeta theta kappa sigma omega lambda) -RANDOM_NUM=$(printf "%02d" $((RANDOM % 100))) -ALIAS_PREFIX="${RANDOM_WORD}${RANDOM_NUM}" - -NAMESPACE=$(namespace) -if [ -z "$NAMESPACE" ]; then - echo "Error: No namespace found. Is anvillm running?" - exit 1 -fi - -AGENT_MOUNT="agent" - -echo "Creating writer session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $DOC_PATH role=writer tasks=git,plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -WRITER_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) - -echo "Creating techeditor session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $DOC_PATH role=techeditor tasks=git,plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -EDITOR_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) - -echo "${ALIAS_PREFIX}-writer" | 9p write $AGENT_MOUNT/$WRITER_ID/alias -echo "${ALIAS_PREFIX}-techeditor" | 9p write $AGENT_MOUNT/$EDITOR_ID/alias - -cat < - -set -e - -if [ $# -ne 2 ]; then - echo "Usage: $0 " - echo " backend: claude, kiro-cli" - exit 1 -fi - -BACKEND="$1" -DOC_PATH="$2" - -# Generate random alias prefix -RANDOM_WORD=$(shuf -n1 -e alpha beta gamma delta zeta theta kappa sigma omega lambda) -RANDOM_NUM=$(printf "%02d" $((RANDOM % 100))) -ALIAS_PREFIX="${RANDOM_WORD}${RANDOM_NUM}" - -NAMESPACE=$(namespace) -if [ -z "$NAMESPACE" ]; then - echo "Error: No namespace found. Is anvillm running?" - exit 1 -fi - -AGENT_MOUNT="agent" - -echo "Creating research session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $DOC_PATH role=research tasks=plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -RESEARCH_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) -echo " ID: $RESEARCH_ID" - -echo "Creating architect session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $DOC_PATH role=architect tasks=git,plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -ARCH_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) -echo " ID: $ARCH_ID" - -echo "Creating techeditor session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $DOC_PATH role=techeditor tasks=git,plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -EDITOR_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) -echo " ID: $EDITOR_ID" - -# Set aliases -echo "${ALIAS_PREFIX}-research" | 9p write $AGENT_MOUNT/$RESEARCH_ID/alias -echo "${ALIAS_PREFIX}-architect" | 9p write $AGENT_MOUNT/$ARCH_ID/alias -echo "${ALIAS_PREFIX}-techeditor" | 9p write $AGENT_MOUNT/$EDITOR_ID/alias - -# Research context -cat < - -set -e - -if [ $# -ne 2 ]; then - echo "Usage: $0 " - echo " backend: claude, kiro-cli" - exit 1 -fi - -BACKEND="$1" -WORKDIR="$2" - -RANDOM_WORD=$(shuf -n1 -e alpha beta gamma delta zeta theta kappa sigma omega lambda) -RANDOM_NUM=$(printf "%02d" $((RANDOM % 100))) -ALIAS_NAME="${RANDOM_WORD}${RANDOM_NUM}-research" - -NAMESPACE=$(namespace) -if [ -z "$NAMESPACE" ]; then - echo "Error: No namespace found. Is anvillm running?" - exit 1 -fi - -AGENT_MOUNT="agent" - -echo "Creating research session..." -before=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -echo "new $BACKEND $WORKDIR role=research tasks=plan9,agent-q" | 9p write $AGENT_MOUNT/ctl -after=$(9p read $AGENT_MOUNT/list | awk '{print $1}' | sort) -RESEARCH_ID=$(comm -13 <(echo "$before") <(echo "$after") | head -1) - -echo "$ALIAS_NAME" | 9p write $AGENT_MOUNT/$RESEARCH_ID/alias - -cat <(Acme)"] + TUI["TUI"] + Emacs["Emacs"] + Web["Web"] + end + + subgraph Core["Core Services"] + anvilsrv["anvilsrv
(9P daemon)"] + anvilmcp["anvilmcp
(MCP server)"] + anvilwebgw["anvilwebgw
(API proxy)"] + end + + subgraph Backends + Claude["Claude"] + Kiro["Kiro"] + ollie["ollie"] + Ollama["Ollama"] + end + + Assist -->|9P| anvilsrv + TUI -->|9P| anvilsrv + Emacs -->|9P| anvilsrv + Web -->|9P| anvilsrv + + anvilsrv --> Claude + anvilsrv --> Kiro + anvilsrv --> ollie + ollie --> Ollama diff --git a/docs/diagrams/architecture.svg b/docs/diagrams/architecture.svg new file mode 100644 index 0000000..38de831 --- /dev/null +++ b/docs/diagrams/architecture.svg @@ -0,0 +1 @@ +

Backends

Core Services

Clients

9P

9P

9P

9P

Assist
(Acme)

TUI

Emacs

Web

anvilsrv
(9P daemon)

anvilmcp
(MCP server)

anvilwebgw
(API proxy)

Claude

Kiro

ollie

Ollama

\ No newline at end of file diff --git a/docs/diagrams/automation-workflow.mmd b/docs/diagrams/automation-workflow.mmd new file mode 100644 index 0000000..0d1b82f --- /dev/null +++ b/docs/diagrams/automation-workflow.mmd @@ -0,0 +1,36 @@ +sequenceDiagram + participant User + participant Taskmaster + participant Beads + participant Conductor + participant Bot1 as Bot 1 + participant Bot2 as Bot 2 + participant Bot3 as Bot 3 + + User->>Taskmaster: Project plan + Taskmaster->>Beads: Create task graph + Beads-->>Taskmaster: Top-level bead ID + Taskmaster-->>User: Tasks created + + User->>Conductor: Top-level bead ID + Conductor->>Beads: Analyze dependencies + + Conductor->>Bot1: Assign task (no blockers) + Conductor->>Bot2: Assign task (no blockers) + + Bot1->>Bot1: Work on task + Bot1-->>Conductor: Task complete + + Conductor->>Beads: Check dependencies + Conductor->>Bot3: Assign task (blockers resolved) + + Bot2->>Bot2: Work on task + Bot2-->>Conductor: Task complete + + Bot3->>Bot3: Work on task + Bot3-->>Conductor: Task complete + + Conductor->>Beads: Check all child beads + Beads-->>Conductor: All complete + Conductor->>Beads: Mark top-level complete + Conductor-->>User: All work done diff --git a/docs/diagrams/automation-workflow.svg b/docs/diagrams/automation-workflow.svg new file mode 100644 index 0000000..8046175 --- /dev/null +++ b/docs/diagrams/automation-workflow.svg @@ -0,0 +1 @@ +Bot 3Bot 2Bot 1ConductorBeadsTaskmasterUserBot 3Bot 2Bot 1ConductorBeadsTaskmasterUserProject planCreate task graphTop-level bead IDTasks createdTop-level bead IDAnalyze dependenciesAssign task (no blockers)Assign task (no blockers)Work on taskTask completeCheck dependenciesAssign task (blockers resolved)Work on taskTask completeWork on taskTask completeCheck all child beadsAll completeMark top-level completeAll work done \ No newline at end of file diff --git a/docs/diagrams/client-interactions.mmd b/docs/diagrams/client-interactions.mmd new file mode 100644 index 0000000..0cff313 --- /dev/null +++ b/docs/diagrams/client-interactions.mmd @@ -0,0 +1,9 @@ +graph TB + Assist["Assist"] + TUI["TUI"] + Script["Script"] + FS["agent/
(9P filesystem)"] + + Assist -->|9p read
agent/list| FS + TUI -->|9p write
agent/ctl| FS + Script -->|9p read
agent/events| FS diff --git a/docs/diagrams/client-interactions.svg b/docs/diagrams/client-interactions.svg new file mode 100644 index 0000000..acab006 --- /dev/null +++ b/docs/diagrams/client-interactions.svg @@ -0,0 +1 @@ +

9p read
agent/list

9p write
agent/ctl

9p read
agent/events

Assist

TUI

Script

agent/
(9P filesystem)

\ No newline at end of file diff --git a/docs/diagrams/crash-recovery.mmd b/docs/diagrams/crash-recovery.mmd new file mode 100644 index 0000000..7e5fd3b --- /dev/null +++ b/docs/diagrams/crash-recovery.mmd @@ -0,0 +1,10 @@ +graph LR + running1[running] + error[error] + running2[running] + state[context
alias
cwd
conversation] + + running1 -->|crash| error + error -->|preserves| state + state -->|restored +
auto-resume| running2 + error -->|5s wait| running2 diff --git a/docs/diagrams/crash-recovery.svg b/docs/diagrams/crash-recovery.svg new file mode 100644 index 0000000..adaa9d4 --- /dev/null +++ b/docs/diagrams/crash-recovery.svg @@ -0,0 +1 @@ +

crash

preserves

restored +
auto-resume

5s wait

running

error

running

context
alias
cwd
conversation

\ No newline at end of file diff --git a/docs/diagrams/events.mmd b/docs/diagrams/events.mmd new file mode 100644 index 0000000..cb8c975 --- /dev/null +++ b/docs/diagrams/events.mmd @@ -0,0 +1,9 @@ +graph TB + events["agent/events
(event stream)"] + notif["Notification"] + logger["Logger
(audit)"] + etc["etc."] + + events --> notif + events --> logger + events --> etc diff --git a/docs/diagrams/events.svg b/docs/diagrams/events.svg new file mode 100644 index 0000000..f0d8697 --- /dev/null +++ b/docs/diagrams/events.svg @@ -0,0 +1 @@ +

agent/events
(event stream)

Notification

Logger
(audit)

etc.

\ No newline at end of file diff --git a/docs/diagrams/mailbox-flow.mmd b/docs/diagrams/mailbox-flow.mmd new file mode 100644 index 0000000..c4e2140 --- /dev/null +++ b/docs/diagrams/mailbox-flow.mmd @@ -0,0 +1,17 @@ +sequenceDiagram + participant User + participant Mailbox as Mailbox System + participant AgentA as Agent A (Claude) + participant AgentB as Agent B (Kiro) + + User->>Mailbox: write user/mail
{"to":"agent-a",...} + Mailbox->>AgentA: agent-a/inbox + + AgentA->>Mailbox: write agent/agent-a/mail
{"to":"agent-b",...} + Mailbox->>AgentB: agent-b/inbox + + AgentB->>Mailbox: write agent/agent-b/mail
{"to":"agent-a",...} + Mailbox->>AgentA: agent-a/inbox + + AgentB->>Mailbox: write agent/agent-b/mail
{"to":"user",...} + Mailbox->>User: user/inbox diff --git a/docs/diagrams/mailbox-flow.svg b/docs/diagrams/mailbox-flow.svg new file mode 100644 index 0000000..a9aff11 --- /dev/null +++ b/docs/diagrams/mailbox-flow.svg @@ -0,0 +1 @@ +Agent B (Kiro)Agent A (Claude)Mailbox SystemUserAgent B (Kiro)Agent A (Claude)Mailbox SystemUserwrite user/mail{"to":"agent-a",...}agent-a/inboxwrite agent/agent-a/mail{"to":"agent-b",...}agent-b/inboxwrite agent/agent-b/mail{"to":"agent-a",...}agent-a/inboxwrite agent/agent-b/mail{"to":"user",...}user/inbox \ No newline at end of file diff --git a/docs/diagrams/sandbox-config.mmd b/docs/diagrams/sandbox-config.mmd new file mode 100644 index 0000000..bc6a33d --- /dev/null +++ b/docs/diagrams/sandbox-config.mmd @@ -0,0 +1,11 @@ +graph TB + global["global.yaml
(Base configuration)"] + backends["backends/*.yaml
(Backend-specific)"] + roles["roles/*.yaml
(Role-specific)
default: roles/default.yaml"] + tasks["tasks/*.yaml
(Task-specific)"] + final["Final Config
(most permissive wins)"] + + global -->|override| backends + backends -->|override| roles + roles -->|override| tasks + tasks --> final diff --git a/docs/diagrams/sandbox-config.svg b/docs/diagrams/sandbox-config.svg new file mode 100644 index 0000000..ab1c949 --- /dev/null +++ b/docs/diagrams/sandbox-config.svg @@ -0,0 +1 @@ +

override

override

override

global.yaml
(Base configuration)

backends/*.yaml
(Backend-specific)

roles/*.yaml
(Role-specific)
default: roles/default.yaml

tasks/*.yaml
(Task-specific)

Final Config
(most permissive wins)

\ No newline at end of file diff --git a/docs/diagrams/session-lifecycle.mmd b/docs/diagrams/session-lifecycle.mmd new file mode 100644 index 0000000..c541ed2 --- /dev/null +++ b/docs/diagrams/session-lifecycle.mmd @@ -0,0 +1,16 @@ +stateDiagram-v2 + [*] --> starting + starting --> idle + starting --> error + + idle --> running: userPromptSubmit hook
(user sends prompt) + running --> idle: stop hook
(agent finishes) + + running --> error: crash + error --> starting: restart + + note right of idle + Any state can transition to + stopped or killed (not shown) + stopped can restart → starting + end note diff --git a/docs/diagrams/session-lifecycle.svg b/docs/diagrams/session-lifecycle.svg new file mode 100644 index 0000000..195e0b1 --- /dev/null +++ b/docs/diagrams/session-lifecycle.svg @@ -0,0 +1 @@ +

userPromptSubmit hook
(user sends prompt)

stop hook
(agent finishes)

crash

restart

starting

idle

error

running

Any state can transition to
stopped or killed (not shown)
stopped can restart → starting

\ No newline at end of file