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 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-19 16:11:10 +02:00
parent 1e1769860a
commit 596f658703
3 changed files with 52 additions and 14 deletions

View File

@ -53,16 +53,20 @@ ollie/
chat read: cumulative conversation history
ctl write: stop | <command>
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: <tool>)
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/)
<name>.md r/w: skill SKILL.md content
t/ dir: tool scripts (r/w, backed by ~/.config/ollie/tools/)

View File

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

View File

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