From ecb94064131fc5614d92e807f9852824b34da5c7 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 17 Aug 2026 09:04:57 +0200 Subject: [PATCH] agent/new: fork_at= parameter for historical context forking Sub-agents can now inherit parent context truncated at a specific user turn: fork_at=5 means 'fork from the state after the 5th user message.' Enables backtracking without losing unsummarized context from before a failed approach. Without fork_at (or fork_at=0), full history is inherited as before. --- cmd/olliesrv/internal/fs/spec.go | 10 +++++++--- cmd/olliesrv/internal/fs/support.go | 27 +++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/cmd/olliesrv/internal/fs/spec.go b/cmd/olliesrv/internal/fs/spec.go index d11a831..db26ff9 100644 --- a/cmd/olliesrv/internal/fs/spec.go +++ b/cmd/olliesrv/internal/fs/spec.go @@ -345,13 +345,17 @@ func buildSessionChildren( virtfs.FileNode("new", 0666, virtfs.Doc("Create agent. With prompt=, runs as sub-agent: blocks until done, returns reply."), virtfs.Rdwr(func(ctx context.Context, data []byte) ([]byte, error) { - params, prompt, parentID := parseAgentNewRequest(data) + params, prompt, parentID, forkAt := parseAgentNewRequest(data) params.ParentID = parentID - // Sub-agent inherits parent's context. + // Sub-agent inherits parent's context (optionally truncated at fork_at). if prompt != "" && parentID != "" { if parent := s.FindAgent(parentID); parent != nil { - params.History = agent.RestoreHistoryFromMessages(parent.Messages()) + msgs := parent.Messages() + if forkAt > 0 { + msgs = messagesUpToTurn(msgs, forkAt) + } + params.History = agent.RestoreHistoryFromMessages(msgs) } } diff --git a/cmd/olliesrv/internal/fs/support.go b/cmd/olliesrv/internal/fs/support.go index 1466380..41e438a 100644 --- a/cmd/olliesrv/internal/fs/support.go +++ b/cmd/olliesrv/internal/fs/support.go @@ -9,6 +9,7 @@ import ( "sync" "ollie/cmd/olliesrv/internal/agent" + "ollie/cmd/olliesrv/internal/backend" "ollie/cmd/olliesrv/internal/session" "ollie/toolsrv/protocol" "ollie/virtfs" @@ -90,11 +91,11 @@ var ( // - Space-separated key=value (legacy format from GUI) // // A bare word (no =) is treated as the profile name (legacy positional arg). -// Returns AgentParams, the prompt (empty if not a sub-agent request), and the parent agent ID. -func parseAgentNewRequest(data []byte) (session.AgentParams, string, string) { +// Returns AgentParams, prompt, parent agent ID, and fork_at (user-turn index, 0=all). +func parseAgentNewRequest(data []byte) (session.AgentParams, string, string, int) { s := strings.TrimSpace(string(data)) if s == "" { - return session.AgentParams{Profile: "default"}, "", "" + return session.AgentParams{Profile: "default"}, "", "", 0 } var tokens []string @@ -106,6 +107,7 @@ func parseAgentNewRequest(data []byte) (session.AgentParams, string, string) { p := session.AgentParams{Profile: "default"} var prompt, parent string + var forkAt int for _, tok := range tokens { tok = strings.TrimSpace(tok) if tok == "" { @@ -130,10 +132,27 @@ func parseAgentNewRequest(data []byte) (session.AgentParams, string, string) { prompt = v case "parent": parent = v + case "fork_at": + fmt.Sscanf(v, "%d", &forkAt) } } else if p.Profile == "default" { p.Profile = tok } } - return p, prompt, parent + return p, prompt, parent, forkAt +} + +// messagesUpToTurn returns messages up to (and including) the Nth user turn. +// Turn 1 = first user message and everything before the second user message. +func messagesUpToTurn(msgs []backend.Message, n int) []backend.Message { + userCount := 0 + for i, m := range msgs { + if m.Role == "user" { + userCount++ + if userCount > n { + return msgs[:i] + } + } + } + return msgs }