From de1bf9612d273d63d53949492fb31588098e35c1 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Thu, 16 Apr 2026 22:05:03 +0200 Subject: [PATCH] 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 --- internal/p9/server.go | 5 +++-- internal/p9/sessionfilestore.go | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/p9/server.go b/internal/p9/server.go index 065c099..9f6b6cc 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -83,8 +83,9 @@ type session struct { cancel context.CancelFunc // chatLog is an append-only record of the conversation. Reads are served // directly from this buffer by offset, so tail -f works via polling. - chatLog []byte - chatVers uint32 // incremented on each append; used as Qid.Vers + chatLog []byte + 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 // (state, backend, agent, model, usage, cwd). Bumped on change; // reported via Qid.Vers in stat so tail -f detects truncation/rewrite. diff --git a/internal/p9/sessionfilestore.go b/internal/p9/sessionfilestore.go index e328bed..ad14985 100644 --- a/internal/p9/sessionfilestore.go +++ b/internal/p9/sessionfilestore.go @@ -19,6 +19,7 @@ var sessionFileList = []struct { {"enqueue", 0200}, {"dequeue", 0444}, {"chat", 0444}, + {"offset", 0444}, {"state", 0444}, {"backend", 0666}, {"agent", 0666}, @@ -78,6 +79,8 @@ func (s *SessionFileStore) Get(name string) ([]byte, error) { copy(data, s.sess.chatLog) s.sess.mu.RUnlock() return data, nil + case "offset": + return []byte(s.content("offset")), nil case "dequeue": item, ok := s.sess.core.PopQueue() if !ok { @@ -101,6 +104,9 @@ func (s *SessionFileStore) Put(name string, data []byte) error { } switch name { 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.trackMutable() @@ -174,6 +180,8 @@ func (s *SessionFileStore) content(name string) string { return s.sess.core.ListServers() + "\n" case "systemprompt": return s.sess.core.SystemPrompt() + case "offset": + return fmt.Sprintf("%d\n", s.sess.chatOffset) } return "" }