9p: expose offset file in session directory

Records the byte position of chat EOF immediately before each prompt
is submitted. Clients can read offset after a turn completes and use
tail -c +$((offset+1)) to extract just the new output without needing
a dedicated reply file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-16 22:05:03 +02:00
parent e277937619
commit de1bf9612d
2 changed files with 11 additions and 2 deletions

View File

@ -83,8 +83,9 @@ type session struct {
cancel context.CancelFunc cancel context.CancelFunc
// chatLog is an append-only record of the conversation. Reads are served // chatLog is an append-only record of the conversation. Reads are served
// directly from this buffer by offset, so tail -f works via polling. // directly from this buffer by offset, so tail -f works via polling.
chatLog []byte chatLog []byte
chatVers uint32 // incremented on each append; used as Qid.Vers chatVers uint32 // incremented on each append; used as Qid.Vers
chatOffset int // byte position of chat EOF immediately before last prompt submit
// mutableVers tracks changes to tailable mutable-state files // mutableVers tracks changes to tailable mutable-state files
// (state, backend, agent, model, usage, cwd). Bumped on change; // (state, backend, agent, model, usage, cwd). Bumped on change;
// reported via Qid.Vers in stat so tail -f detects truncation/rewrite. // reported via Qid.Vers in stat so tail -f detects truncation/rewrite.

View File

@ -19,6 +19,7 @@ var sessionFileList = []struct {
{"enqueue", 0200}, {"enqueue", 0200},
{"dequeue", 0444}, {"dequeue", 0444},
{"chat", 0444}, {"chat", 0444},
{"offset", 0444},
{"state", 0444}, {"state", 0444},
{"backend", 0666}, {"backend", 0666},
{"agent", 0666}, {"agent", 0666},
@ -78,6 +79,8 @@ func (s *SessionFileStore) Get(name string) ([]byte, error) {
copy(data, s.sess.chatLog) copy(data, s.sess.chatLog)
s.sess.mu.RUnlock() s.sess.mu.RUnlock()
return data, nil return data, nil
case "offset":
return []byte(s.content("offset")), nil
case "dequeue": case "dequeue":
item, ok := s.sess.core.PopQueue() item, ok := s.sess.core.PopQueue()
if !ok { if !ok {
@ -101,6 +104,9 @@ func (s *SessionFileStore) Put(name string, data []byte) error {
} }
switch name { switch name {
case "prompt": case "prompt":
s.sess.mu.Lock()
s.sess.chatOffset = len(s.sess.chatLog)
s.sess.mu.Unlock()
s.sess.core.Submit(s.sess.ctx, input, s.makePublish()) s.sess.core.Submit(s.sess.ctx, input, s.makePublish())
s.sess.trackMutable() s.sess.trackMutable()
@ -174,6 +180,8 @@ func (s *SessionFileStore) content(name string) string {
return s.sess.core.ListServers() + "\n" return s.sess.core.ListServers() + "\n"
case "systemprompt": case "systemprompt":
return s.sess.core.SystemPrompt() return s.sess.core.SystemPrompt()
case "offset":
return fmt.Sprintf("%d\n", s.sess.chatOffset)
} }
return "" return ""
} }