From 596f658703c8fb332927c4eb640e77b2041547a4 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Sun, 19 Apr 2026 16:11:10 +0200 Subject: [PATCH] 9p: add statewait/usagewait/ctxszwait/cwdwait; rename enqueue/dequeue *wait files block on read until the corresponding value changes, then return the new value. enqueue/dequeue renamed to fifo.in/fifo.out. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 10 +++++--- internal/p9/server.go | 14 +++++------ internal/p9/sessionfilestore.go | 42 +++++++++++++++++++++++++++++---- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d74a137..ea5818a 100644 --- a/README.md +++ b/README.md @@ -53,16 +53,20 @@ ollie/ chat read: cumulative conversation history ctl write: stop | ctxsz read: estimated context size vs context window - dequeue read: pop the next queued prompt - enqueue write: queue a prompt for later execution + ctxszwait read: blocks until ctxsz changes; returns new value + cwd r/w: working directory for tool execution + cwdwait read: blocks until cwd changes; returns new value + fifo.in write: queue a prompt for later execution + fifo.out read: pop the next queued prompt mcp read: MCP server list model r/w: active model name models read: available models from the backend prompt write: submit a prompt to the agent state read: current agent state (idle, thinking, calling: ) + statewait read: blocks until state changes; returns new value systemprompt read: fully rendered system prompt for this session usage read: token counts (input, output, requests; [estimated] if not reported by backend) - cwd r/w: working directory for tool execution and system prompt + usagewait read: blocks until usage changes; returns new value sk/ dir: skills (r/w, from OLLIE_SKILLS_PATH or ~/.config/ollie/skills/) .md r/w: skill SKILL.md content t/ dir: tool scripts (r/w, backed by ~/.config/ollie/tools/) diff --git a/internal/p9/server.go b/internal/p9/server.go index b7077c1..a53bfb4 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -14,8 +14,8 @@ // {session-id}/ rm -r to kill session; mv to rename // ctl (write) session control: stop, kill, rn, compact, clear // prompt (write) submit a prompt to the agent -// enqueue (write) queue a prompt for later execution -// dequeue (read) pop the next queued prompt +// fifo.in (write) queue a prompt for later execution +// fifo.out (read) pop the next queued prompt // chat (r/w) cumulative chat history; write to save a transcript to tr/ // state (read) current agent state // backend (r/w) active backend name @@ -718,8 +718,8 @@ func (s *Server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall { plog.Debug("Rread session not found: %s", parts[1]) return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Count: 0} } - // dequeue: non-zero offset is the trailing EOF read after a successful pop. - if parts[2] == "dequeue" && fc.Offset > 0 { + // fifo.out: non-zero offset is the trailing EOF read after a successful pop. + if parts[2] == "fifo.out" && fc.Offset > 0 { return &plan9.Fcall{Type: plan9.Rread, Tag: fc.Tag, Count: 0} } content, err := store.Get(parts[2]) @@ -945,7 +945,7 @@ func (s *Server) isAsyncWrite(path string) bool { return false } switch pathBase(path) { - case "prompt", "enqueue", "ctl": + case "prompt", "fifo.in", "ctl": return true } return false @@ -1449,9 +1449,9 @@ func (s *Server) makeStat(path string) plan9.Dir { } } else { switch base { - case "ctl", "prompt", "enqueue": + case "ctl", "prompt", "fifo.in": mode = 0200 - case "chat", "state", "usage", "ctxsz", "models", "mcp", "dequeue": + case "chat", "state", "usage", "ctxsz", "models", "mcp", "fifo.out": mode = 0444 case "backend", "agent", "model", "cwd": mode = 0666 diff --git a/internal/p9/sessionfilestore.go b/internal/p9/sessionfilestore.go index de57789..1763920 100644 --- a/internal/p9/sessionfilestore.go +++ b/internal/p9/sessionfilestore.go @@ -18,17 +18,21 @@ var sessionFileList = []struct { }{ {"ctl", 0200}, {"prompt", 0200}, - {"enqueue", 0200}, - {"dequeue", 0444}, + {"fifo.in", 0200}, + {"fifo.out", 0444}, {"chat", 0666}, {"offset", 0444}, {"state", 0444}, + {"statewait", 0444}, {"backend", 0666}, {"agent", 0666}, {"model", 0666}, {"cwd", 0666}, + {"cwdwait", 0444}, {"usage", 0444}, + {"usagewait", 0444}, {"ctxsz", 0444}, + {"ctxszwait", 0444}, {"models", 0444}, {"mcp", 0444}, {"systemprompt", 0444}, @@ -66,6 +70,8 @@ func (s *SessionFileStore) Stat(name string) (os.FileInfo, error) { s.sess.mu.RLock() size = int64(len(s.sess.chatLog)) s.sess.mu.RUnlock() + case "statewait", "usagewait", "ctxszwait", "cwdwait": + // Blocking reads; size is unknown until resolved. default: size = int64(len(s.content(name))) } @@ -85,12 +91,40 @@ func (s *SessionFileStore) Get(name string) ([]byte, error) { return data, nil case "offset": return []byte(s.content("offset")), nil - case "dequeue": + case "fifo.out": item, ok := s.sess.core.PopQueue() if !ok { return nil, nil } return []byte(item), nil + case "statewait": + current := s.sess.core.State() + v, ok := s.sess.core.WaitChange(s.sess.ctx, agent.WatchState, current) + if !ok { + return nil, nil + } + return []byte(v + "\n"), nil + case "usagewait": + current := s.sess.core.Usage() + v, ok := s.sess.core.WaitChange(s.sess.ctx, agent.WatchUsage, current) + if !ok { + return nil, nil + } + return []byte(v + "\n"), nil + case "ctxszwait": + current := s.sess.core.CtxSz() + v, ok := s.sess.core.WaitChange(s.sess.ctx, agent.WatchCtxSz, current) + if !ok { + return nil, nil + } + return []byte(v + "\n"), nil + case "cwdwait": + current := s.sess.core.CWD() + v, ok := s.sess.core.WaitChange(s.sess.ctx, agent.WatchCWD, current) + if !ok { + return nil, nil + } + return []byte(v + "\n"), nil default: for _, f := range sessionFileList { if f.name == name { @@ -113,7 +147,7 @@ func (s *SessionFileStore) Put(name string, data []byte) error { case "prompt": s.sess.core.Submit(s.sess.ctx, input, s.makePublish()) - case "enqueue": + case "fifo.in": s.sess.core.Queue(input) case "ctl":