doc: add pair programming section, update evolution, fix o new syntax

- README: add 'Pair programming' to capabilities table
- usage.md: full section on observer agent setup + wiring patterns
- usage.md: update o new syntax to sname/aname form
- evolution.md: document feed + BlockOnce/Stream refactor session
This commit is contained in:
Levi Neely 2026-08-13 21:18:25 +02:00
parent 4061e494b3
commit 51d0576680
3 changed files with 111 additions and 5 deletions

View File

@ -57,6 +57,7 @@ Everything lives under `$XDG_CONFIG_HOME/ollie/` (default: `~/.config/ollie/`)
| **Terminal TUI** | `o tui` — tmux layout, no widgets |
| **One-shot LLM** | `o generate "explain monads"` or `echo "prompt" \| o generate` |
| **Run an agent** | Create a session + agent via 9P — then connect with any frontend ([ellie](doc/ellie.md), KDE (GUI/KRunner/Kate), acme, `o tui`) |
| **Pair programming** | Wire an observer agent to your coding session via `feed` — real-time observations as you work ([doc](doc/usage.md#pair-programming--observer-agents)) |
| **Remote execution** | Set `remote=user@host` in session config |
| **Background processes** | Pass `"background": true` to any tool — runs with no timeout, output pushed to agent prompt on completion |
| **Parallel execution** | Non-conflicting tool calls within a turn run in parallel (scope-based conflict scheduling) |

View File

@ -52,6 +52,8 @@ gantt
Meta-only tool definitions :done, 2026-08-11, 2d
Bypass via 9P :done, 2026-08-11, 1d
fs/ flattening :done, 2026-08-11, 1d
Feed + observer agents :done, 2026-08-13, 1d
BlockOnce/Stream refactor :done, 2026-08-13, 1d
```
## Phase 1: Monorepo Bootstrap (Apr 11)
Started as independent git repos unified under a monorepo with submodules.
@ -1402,5 +1404,48 @@ Everything that was built and then killed, in roughly chronological order.
| Named sandbox profiles (default, restricted, remote) | One config: `sandbox.yaml`. Multi-profile system was unused complexity |
| `TurnCtx` struct | Eliminated; loop functions became methods on `*Agent` |
| Bypass via custom Unix socket protocol | Replaced by bypass via 9P namespace |
## Feed file + Observer agents + BlockOnce/Stream refactor (Aug 13)
Three related changes in one session. Net **+278 SLOC** across 21 files.
### What was added
**`feed` file** — a change-detecting blocking read file on every agent. Write data in, internal consumer submits it as a prompt. Dedup built into the `BlockOnce` handler: same data written twice never wakes the reader. Enables real-time pair programming — an observer agent watches a human or another agent code.
**Observer agent** (`data/agents/observer.json`, `data/prompts/agent-observer.md`) — read-only agent profile. Only loads `file_read`, `file_grep`, `file_glob`, `reasoning_think`. Prompt explicitly forbids writes. Receives diffs via feed, makes terse observations.
**`ConsumeFeed`** — plain function that dials the 9P server via `lib9p` and reads from the agent's own feed file in a loop. Each read blocks until feed changes. Same pattern as `o read -l`.
### What was refactored
**`virtfs.BlockOnce(readFn, signalFn)`** — previously a raw handler that took `(ctx, base)` and had to implement its own blocking. Now the framework handles the block-until-changed loop: call `readFn()`, compare hash to base, wait on `signalFn()` channel, repeat. Handlers become two-line closures.
**`virtfs.Stream(readFn, signalFn)`** — same refactor. `streamChat` (40 lines of condvar + mutex + offset tracking) replaced by `a.ChatRead` + `a.ChatSignal` passed to `Stream(...)`.
**Server timeout fallback** — when `BlockOnce` times out (5s, no change), the server falls back to the plain `Read` handler. Frontends get the current value as a heartbeat. Files without `Read` (like feed) return empty.
### What was removed
- `streamChat()` in support.go (replaced by `ChatRead` method on agent)
- `mergeCtx()` in support.go (no longer needed — blocking logic moved into virtfs framework)
- `ObserverFeed` script (replaced by the `feed` file)
- `WaitChange` usage outside filesystem handlers (feed consumer uses lib9p instead)
### What was fixed
- **XDG fallback** in prompt resolver — `$XDG_CONFIG_HOME` now defaults to `$HOME/.config` when unset
- **Duplicate tool headers** — `renderTools` skipped the generated `## name` when the tool prompt already has one
- **FIFO drain** — queued prompts no longer orphaned on interrupt/panic/toolsrv failure
- **Session restore ordering** — moved after 9P listener starts so feed consumers can connect
### Dead ends (killed during the session)
| Attempt | Why killed |
|---|---|
| Custom channel-based Feed with its own signal infrastructure | Duplicated the agent's existing signalCh mechanism |
| `Stream` mode for feed | Feed is a discrete value, not a byte stream |
| Internal WaitChange-based consumer | Leaked internal plumbing; should be a plain 9P client |
| `BlockOnceRaw` as primary API | Forced handlers to implement blocking themselves |
| Concurrent-by-default tool execution | Rejected: turns one reasoning step into N generation cycles with partial info — sequential but slower and more expensive |

View File

@ -37,9 +37,8 @@ Installed to `~/.local/bin/o` by `just install-data`.
### Quick start
```sh
o new myproject # create a session
o myproject new default # create an agent in that session
o myproject/default tui # launch the tmux TUI
o myproj/default new # create session + agent
o myproj/default tui # launch the tmux TUI
```
Or without the TUI:
@ -80,10 +79,9 @@ The slash in `<session>/<agent>` disambiguates session-only from session+agent.
| Command | Context | Description |
|---|---|---|
| `o ls` | root | List sessions, models, backends |
| `o new <session> [cwd]` | root | Create a new session |
| `o generate <prompt>` | root | One-shot generation (no session needed) |
| `o <sess>/<agent> new [cwd]` | agent | Create session (if needed) + agent |
| `o <sess> ls` | session | List session contents |
| `o <sess> new <agent> [cwd]` | session | Create a new agent |
| `o <sess> ctl <cmd>` | session | Session control |
| `o <sess>/<agent> prompt` | agent | Interactive multi-line prompt REPL |
| `o <sess>/<agent> tui` | agent | Launch tmux TUI |
@ -225,6 +223,68 @@ context purely from its positional arguments.
---
## Pair programming — observer agents
An observer agent watches your coding session and makes real-time observations: bugs, missed error handling, security issues. It uses the `feed` file — a change-detecting input present on every agent. The same mechanism works whether a human or another agent is coding.
### Setup
```sh
# Create the observer (use the observer profile for read-only tools):
o myproj/obs new /path/to/repo
o myproj/obs ctl agent observer
```
### Wire a human coding session
Poll git diffs and pipe into the observer's feed. The `feed` file deduplicates — same diff written twice is ignored:
```sh
while :; do git diff HEAD --; sleep 5; done | o myproj/obs write feed
```
Read observations in another terminal:
```sh
o myproj/obs read chat
```
### Wire an agent coding session
Use `statewait` as the trigger — it blocks until the coder's state changes (turn completes). Then pipe in the git diff:
```sh
while :; do o myproj/coder read statewait >/dev/null; git diff HEAD --; done | o myproj/obs write feed
```
### How it works
The `feed` file on each agent is a `BlockOnce` file:
- **Write**: stores the data + signals change.
- **Read** (blocking): blocks until the content changes from what it was when you opened. Returns new data once, then EOF.
- **Dedup**: built into the read side. Same content written repeatedly never wakes the reader.
Internally, each agent has a `ConsumeFeed` goroutine that reads from its own feed file (via 9P) and submits new data as prompts. The observer processes each diff it receives, optionally reads surrounding code for context, and produces terse observations.
### Multiple observers
Nothing stops you from running N observers on the same coder:
```sh
# Security-focused observer:
o myproj/security new /path/to/repo
o myproj/security ctl agent observer
# Performance-focused observer (custom prompt):
o myproj/perf new /path/to/repo
# ... configure with a different prompt
# Same feed wiring for both:
while :; do git diff HEAD --; sleep 5; done | tee >(o myproj/security write feed) | o myproj/perf write feed
```
---
## Other front-ends
- **[ellie](ellie.md)** — Emacs session tree, chat, multi-agent