@@ 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
@@ 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