diff --git a/README.md b/README.md
index 7cabe2c..b1e0713 100644
--- a/README.md
+++ b/README.md
@@ -94,16 +94,23 @@ Single Go module with one Git submodule (kde).
```mermaid
graph TB
- subgraph Frontends
- ACME[Plan 9 acme]
+ subgraph "Frontends (all use o / ollie-9p)"
+ TUI["o tui
(tmux + o read/ctl)"]
+ KDE[KDE GUI
ollie-9p native client]
+ KATE[Kate plugin]
EMACS[Emacs
ellie.el]
- KDE[KDE
GUI / Kate / KRunner]
+ ACME[Plan 9 acme]
+ SCRIPTS[Shell scripts
o read · o ctl · o write]
+ end
+
+ subgraph "o CLI"
+ O["o
path resolution · ctl dispatch
prompt REPL · TUI composition"]
end
subgraph "olliesrv"
P9[9P Filesystem
session/ namespace]
SM[Session Manager]
- AG[agent.Agent
loop · compaction
prompt templates]
+ AG[agent.Agent
loop · compaction
prompt assembly]
ROUTE[Router
complete · generate · route]
TOOLS[toolsrv.Server
dynamic dispatch · sandbox]
end
@@ -121,7 +128,9 @@ graph TB
REMOTE[ollie-remote
via SSH · no embedded tools]
end
- ACME & EMACS & KDE --> P9
+ TUI & SCRIPTS & EMACS & ACME --> O
+ O --> P9
+ KDE & KATE --> P9
P9 --> SM
SM --> AG
AG --> ROUTE
@@ -134,7 +143,9 @@ graph TB
```
`olliesrv` exposes the agent runtime over a single surface: a 9P2000 filesystem.
-KDE GUI, Kate plugin, and KRunner all use 9P internally. For a deep dive into the
+`o` is the CLI layer that all text-based frontends build on — it handles path
+resolution, context, and ctl dispatch. The KDE GUI and Kate plugin use `ollie-9p`
+(the native 9P client) directly for performance. For a deep dive into the
filesystem, see [`doc/resources/9p.md`](doc/resources/9p.md).
## Credits
diff --git a/contrib/elisp/el/ellie.el b/contrib/elisp/el/ellie.el
index 1a72d0e..3b773fe 100644
--- a/contrib/elisp/el/ellie.el
+++ b/contrib/elisp/el/ellie.el
@@ -106,6 +106,18 @@ Returns nil on any error."
(unless (zerop rc)
(user-error "ollie-9p write %s failed (exit %d)" path rc)))))
+(defun ellie--9p-rdwr (path text)
+ "Write TEXT to PATH via ollie-9p rdwr and return the response.
+Returns nil on any error."
+ (condition-case nil
+ (with-temp-buffer
+ (insert text)
+ (if (= (call-process-region (point-min) (point-max)
+ "ollie-9p" t t nil "rdwr" path) 0)
+ (buffer-string)
+ nil))
+ (error nil)))
+
(defun ellie--9p-ls (path)
"Return list of entry names under PATH via ollie-9p ls.
Returns nil on error."
@@ -496,8 +508,8 @@ Key bindings:
(defun ellie-show-system-prompt ()
"Display the rendered system prompt for the current session in a buffer."
(interactive)
- (let* ((path (ellie--session-file "systemprompt"))
- (text (ellie--9p-read path)))
+ (let* ((path (ellie--session-file "ctl"))
+ (text (ellie--9p-rdwr path "systemprompt")))
(unless text
(user-error "Could not read system prompt"))
(let ((buf (get-buffer-create "*ellie-system-prompt*")))
diff --git a/contrib/elisp/ellie.el b/contrib/elisp/ellie.el
index 32cf696..d9ae4ef 100644
--- a/contrib/elisp/ellie.el
+++ b/contrib/elisp/ellie.el
@@ -89,6 +89,18 @@ Returns nil on any error."
(unless (zerop rc)
(user-error "ollie-9p write %s failed (exit %d)" path rc)))))
+(defun ellie--9p-rdwr (path text)
+ "Write TEXT to PATH via ollie-9p rdwr and return the response.
+Returns nil on any error."
+ (condition-case nil
+ (with-temp-buffer
+ (insert text)
+ (if (= (call-process-region (point-min) (point-max)
+ "ollie-9p" t t nil "rdwr" path) 0)
+ (buffer-string)
+ nil))
+ (error nil)))
+
(defun ellie--9p-ls (path)
"Return list of entry names under PATH via ollie-9p ls.
Returns nil on error."
@@ -744,8 +756,8 @@ QUEUED non-nil means the prompt will be enqueued, not submitted immediately."
(defun ellie-show-system-prompt ()
"Display the rendered system prompt for the current agent."
(interactive)
- (let* ((path (ellie--session-file "systemprompt"))
- (text (ellie--9p-read path)))
+ (let* ((path (ellie--session-file "ctl"))
+ (text (ellie--9p-rdwr path "systemprompt")))
(unless text
(user-error "Could not read system prompt"))
(let ((buf (get-buffer-create "*ellie-system-prompt*")))