~kris/9p

llm9p

0f0dfed7f927302ad0f7a0832d97a6a3bbfe4980 — pdfinn 7 months ago 68199d2
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 <noreply@anthropic.com>
2 files changed, 228 insertions(+), 7 deletions(-)

M CLAUDE.md
M README.md
M CLAUDE.md => CLAUDE.md +63 -3
@@ 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

M README.md => README.md +165 -4
@@ 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