From 0f0dfed7f927302ad0f7a0832d97a6a3bbfe4980 Mon Sep 17 00:00:00 2001 From: pdfinn Date: Fri, 23 Jan 2026 09:03:57 +0700 Subject: [PATCH] docs: Add 9P introduction and Infernode instructions - Add "What is 9P?" section explaining the protocol for newcomers - Add comprehensive Infernode (Inferno OS) mounting instructions - Add troubleshooting section with common issues and solutions - Add verified test cases section documenting tested scenarios - Add "How It Works" explanation of the request flow - Include tips for Infernode users (use 127.0.0.1, create mount point first) Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 66 ++++++++++++++++++++- README.md | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 228 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 39b765d2fcddb614cedd90bb5c161a58216aa247..1681442a6abe7c14f21277df5678d4b5f301f6aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -191,19 +191,54 @@ ANTHROPIC_API_KEY=sk-... ./llm9p -debug This logs all 9P messages sent and received. -### Test with 9p Client +### Test with 9p Client (plan9port) ```bash # Using 9pfuse 9pfuse localhost:5640 /mnt/llm -# Using Plan 9's 9p tool +# Using Plan 9's 9p tool (no mount needed) 9p -a localhost:5640 ls llm 9p -a localhost:5640 read llm/model +9p -a localhost:5640 read llm/temperature +9p -a localhost:5640 write llm/ask "What is 2+2?" +9p -a localhost:5640 read llm/ask # Returns "4" +9p -a localhost:5640 read llm/tokens # Returns token count + +# Multi-turn conversation +9p -a localhost:5640 write llm/ask "Remember the number 42" +9p -a localhost:5640 write llm/ask "What number did I just mention?" +9p -a localhost:5640 read llm/ask # Returns "42" + +# Add system message (e.g., persona) +9p -a localhost:5640 write llm/context "Respond like a pirate" 9p -a localhost:5640 write llm/ask "Hello" -9p -a localhost:5640 read llm/ask +9p -a localhost:5640 read llm/ask # Pirate-style response + +# Reset conversation +9p -a localhost:5640 write llm/new "reset" ``` +### Test with Infernode (Inferno OS) + +```bash +# Start infernode (from infernode directory) +./emu + +# Inside infernode shell: +mkdir /n/llm +mount -A tcp!127.0.0.1!5640 /n/llm +ls -l /n/llm +cat /n/llm/model +echo 'What is the capital of France?' > /n/llm/ask +cat /n/llm/ask +``` + +**Infernode Notes:** +- Use `127.0.0.1` not `localhost` (DNS resolution differs) +- Create mount point before mounting: `mkdir /n/llm` +- The `-A` flag enables anonymous auth + ### Common Issues **"file not found"** @@ -254,6 +289,30 @@ func (f *MyFile) Stat() protocol.Stat { } ``` +## Verified Test Cases + +The following scenarios have been tested and verified working: + +### plan9port (9p tool) +- [x] `ls llm` - List filesystem root +- [x] `read llm/model` - Returns model name +- [x] `read llm/temperature` - Returns temperature +- [x] `read llm/tokens` - Returns 0 initially, updates after queries +- [x] `read llm/_example` - Returns usage examples +- [x] `write llm/temperature "0.5"` - Updates temperature setting +- [x] `write llm/ask "What is 2+2?"` followed by `read llm/ask` - Returns "4" +- [x] Multi-turn conversation maintains context +- [x] `write llm/context "Respond like a pirate"` - System message works +- [x] `write llm/new "reset"` - Clears conversation history + +### Infernode (Inferno OS) +- [x] `mount -A tcp!127.0.0.1!5640 /n/llm` - Mounts successfully +- [x] `ls -l /n/llm` - Lists all files with correct permissions +- [x] `cat /n/llm/model` - Returns model name +- [x] `cat /n/llm/temperature` - Returns temperature +- [x] `echo 'prompt' > /n/llm/ask` followed by `cat /n/llm/ask` - Full LLM interaction works +- [x] LLM correctly identifies client as Inferno OS when asked + ## Future Enhancements - [ ] Multiple conversation support (via subdirectories) @@ -269,3 +328,4 @@ func (f *MyFile) Stat() protocol.Stat { - [9P Protocol Specification](http://man.cat-v.org/plan_9/5/intro) - [Anthropic API Documentation](https://docs.anthropic.com/) - [Plan 9 from User Space](https://9fans.github.io/plan9port/) +- [Infernode](https://github.com/NERVsystems/infernode) - Hosted Inferno OS with native 9P support diff --git a/README.md b/README.md index 6a48c7d31e57aca9f52db8cb750ded0fc24c6a79..7827b9fd54d7eec90c7c97c5a5e341f26d3ec145 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,16 @@ An LLM (Claude) exposed as a 9P filesystem. llm9p enables users, scripts, and AI agents to interact with an LLM through standard filesystem operations. Write a prompt to a file, read the response from the same file. +## What is 9P? + +9P is a simple, lightweight network filesystem protocol originally developed for Plan 9 from Bell Labs. It lets you access remote resources as if they were local files. This means you can interact with Claude using basic file operations (`cat`, `echo`, `>`, `<`) instead of SDKs or HTTP APIs. + +**Why use 9P for LLM access?** +- **Universal**: Any language or tool that can read/write files can use it +- **Scriptable**: Chain LLM calls with standard Unix pipes and shell scripts +- **Composable**: Mount multiple 9P services and combine them +- **Simple**: No libraries, no dependencies, just files + ## Installation ```bash @@ -29,17 +39,74 @@ export ANTHROPIC_API_KEY=sk-ant-... ### Mount the Filesystem -Using 9pfuse (Plan 9 from User Space): +There are several ways to mount the filesystem depending on your environment. + +#### Option 1: Plan 9 from User Space (plan9port) + +[plan9port](https://9fans.github.io/plan9port/) provides Plan 9 tools for Unix systems. ```bash +# Install on macOS +brew install plan9port + +# Install on Debian/Ubuntu +sudo apt-get install 9base + +# Mount using 9pfuse mkdir -p /mnt/llm 9pfuse localhost:5640 /mnt/llm + +# Or use the 9p tool directly (no mount needed) +9p -a localhost:5640 ls llm +9p -a localhost:5640 read llm/model +9p -a localhost:5640 write llm/ask "What is 2+2?" +9p -a localhost:5640 read llm/ask +``` + +#### Option 2: Infernode (Inferno OS) + +[Infernode](https://github.com/NERVsystems/infernode) is a hosted Inferno OS environment with native 9P support. This is an excellent way to explore 9P filesystems. + +```sh +# Start infernode +cd /path/to/infernode +./emu + +# Inside infernode shell, mount llm9p +# Note: Use IP address 127.0.0.1, not "localhost" +mkdir /n/llm +mount -A tcp!127.0.0.1!5640 /n/llm + +# List available files +ls -l /n/llm + +# Check current model +cat /n/llm/model + +# Ask a question +echo 'What is the capital of France?' > /n/llm/ask +cat /n/llm/ask + +# View conversation history +cat /n/llm/context + +# Reset conversation +echo reset > /n/llm/new ``` -On macOS with plan9port: +**Infernode Tips:** +- Always use `127.0.0.1` instead of `localhost` for the server address +- Create the mount point with `mkdir /n/llm` before mounting +- The `-A` flag to mount enables anonymous authentication (no auth required) +- Use single quotes around prompts to avoid shell interpretation issues + +#### Option 3: Linux 9P Mount + +Linux has built-in 9P filesystem support via the `9p` kernel module. ```bash -9 mount localhost:5640 /mnt/llm +# Mount via kernel 9p module +sudo mount -t 9p -o port=5640,version=9p2000 localhost /mnt/llm ``` ### Interact with the LLM @@ -139,7 +206,101 @@ cat /mnt/llm/ask - Go 1.21+ - Anthropic API key -- 9P client (9pfuse, plan9port, or native Plan 9) +- 9P client (one of the following): + - [plan9port](https://9fans.github.io/plan9port/) - Plan 9 tools for Unix (macOS, Linux) + - [Infernode](https://github.com/NERVsystems/infernode) - Hosted Inferno OS with native 9P + - Linux 9P kernel module - Built into Linux kernel + - Native Plan 9 or Inferno OS + +## Troubleshooting + +### Port already in use + +```bash +# Check what's using the port +lsof -i :5640 + +# Use a different port +./llm9p -addr :5641 +``` + +### Connection refused + +Ensure the server is running and listening on the expected port: + +```bash +# Start with debug logging to see connections +./llm9p -addr :5640 -debug +``` + +### Infernode: "localhost" not resolving + +Inferno's DNS resolution works differently. Use the IP address directly: + +```sh +# Wrong +mount -A tcp!localhost!5640 /n/llm + +# Correct +mount -A tcp!127.0.0.1!5640 /n/llm +``` + +### Infernode: Mount point doesn't exist + +Create the mount point before mounting: + +```sh +mkdir /n/llm +mount -A tcp!127.0.0.1!5640 /n/llm +``` + +### Permission denied on write + +Some files are read-only by design: +- `tokens` - Read-only (token count from last response) +- `_example` - Read-only (usage examples) +- `stream/chunk` - Read-only (streaming output) + +### Empty response from `ask` + +The `ask` file only contains content after you write a prompt to it: + +```bash +# First write a prompt +echo "Hello" > /mnt/llm/ask + +# Then read the response +cat /mnt/llm/ask +``` + +### API errors + +Check that your API key is set and valid: + +```bash +# Ensure the key is exported +export ANTHROPIC_API_KEY=sk-ant-... + +# Verify it's set +echo $ANTHROPIC_API_KEY +``` + +## How It Works + +1. **Server starts**: llm9p listens for 9P connections on the specified port +2. **Client connects**: A 9P client (9pfuse, Infernode, etc.) connects and negotiates the protocol +3. **Filesystem exposed**: The client sees a virtual filesystem with files like `ask`, `model`, `tokens` +4. **Write prompt**: Writing to `ask` sends the text to Claude via the Anthropic API +5. **Read response**: Reading from `ask` returns Claude's response +6. **State persists**: Conversation history is maintained until you write to `new` + +The 9P protocol handles all the complexity of making this look like a regular filesystem, so any tool that can read and write files can interact with the LLM. + +## Related Projects + +- [Infernode](https://github.com/NERVsystems/infernode) - Hosted Inferno OS with native 9P support +- [plan9port](https://9fans.github.io/plan9port/) - Plan 9 from User Space +- [u9fs](https://github.com/9fans/plan9port/tree/master/src/cmd/9pserve) - 9P file server ## License