From fd20070294db99ac39ed4d253fd392a284742b8d Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 8 Jul 2026 15:55:10 +0200 Subject: [PATCH] refactor: remove 9P mount logic from core execute server Moved all 9P FUSE mount code from core to 9p module using lifecycle hooks. Changes: - Removed 9P-specific fields (mountProc, mountPath, mountEnabled) from execute.Server - Removed WithMount(), MountPath(), startMount(), remountIfStale() functions - Added lifecycle hooks: OnPreDispatch, OnClose, OnEnvSet with corresponding options - Updated SetEnv(), Close(), and Dispatch() to call hooks instead of hardcoded mount logic - Fixed tool descriptions to use $OLLIE_TOOLS_PATH instead of 9P-specific $OLLIE/s/$SID/t paths - Removed unused execute import from agent/commands.go The core execute server is now harness-agnostic. --- pkg/agent/commands.go | 3 +- pkg/tools/execute/code.go | 4 +- pkg/tools/execute/server.go | 106 +++++++----------------------------- 3 files changed, 24 insertions(+), 89 deletions(-) diff --git a/pkg/agent/commands.go b/pkg/agent/commands.go index 3f29e11..f47de2b 100644 --- a/pkg/agent/commands.go +++ b/pkg/agent/commands.go @@ -10,7 +10,6 @@ import ( "strings" "ollie/pkg/config" - "ollie/pkg/tools/execute" ) func (s *agent) handleCommand(ctx context.Context, input string) bool { @@ -176,7 +175,7 @@ func (s *agent) handleCommand(ctx context.Context, input string) bool { return } d := s.newDispatcher() - rt := BuildRuntime(cfg, d, s.cwd, []string{"OLLIE_SESSION_ID=" + s.sessionID, "OLLIE=" + execute.MountPath(s.uname)}) + rt := BuildRuntime(cfg, d, s.cwd, []string{"OLLIE_SESSION_ID=" + s.sessionID}) if rt.CfgBackend != "" { newBe, err := s.newBackend(rt.CfgBackend) if err != nil { diff --git a/pkg/tools/execute/code.go b/pkg/tools/execute/code.go index 5b22a39..6bf590a 100644 --- a/pkg/tools/execute/code.go +++ b/pkg/tools/execute/code.go @@ -107,7 +107,9 @@ var languagePatterns = map[string][]*regexp.Regexp{ // Dispatch routes a named execute tool call. func (e *Server) Dispatch(ctx context.Context, name string, args json.RawMessage) (string, error) { - e.remountIfStale() + if e.OnPreDispatch != nil { + e.OnPreDispatch() + } if len(e.allowExecutors) > 0 && !e.allowExecutors[name] { return "", fmt.Errorf("executor %q not permitted in this agent context", name) } diff --git a/pkg/tools/execute/server.go b/pkg/tools/execute/server.go index fb3b86b..6ff6fce 100644 --- a/pkg/tools/execute/server.go +++ b/pkg/tools/execute/server.go @@ -41,10 +41,10 @@ type Server struct { // step dispatch. Set once at session init via SetLockDir; empty disables locking. lockDir string - // mountProc is the ollie-9p-mount subprocess for this session's FUSE proxy. - mountProc *os.Process - mountPath string - mountEnabled bool + // Hooks for lifecycle events (harnesses like 9P can inject mount logic here) + OnPreDispatch func() + OnClose func() + OnEnvSet func(key, value string) // Strict rejects inline {code} steps; only {tool} steps are allowed. Strict bool @@ -76,8 +76,14 @@ func WithStrict() Option { return func(s *Server) { s.Strict = true } } // WithYolo skips the landrun sandbox. func WithYolo() Option { return func(s *Server) { s.Yolo = true } } -// WithMount enables per-session FUSE mount for 9P identity. -func WithMount() Option { return func(s *Server) { s.mountEnabled = true } } +// WithOnPreDispatch registers a hook called before each Dispatch. +func WithOnPreDispatch(fn func()) Option { return func(s *Server) { s.OnPreDispatch = fn } } + +// WithOnClose registers a hook called during Close. +func WithOnClose(fn func()) Option { return func(s *Server) { s.OnClose = fn } } + +// WithOnEnvSet registers a hook called each time SetEnv is called. +func WithOnEnvSet(fn func(key, value string)) Option { return func(s *Server) { s.OnEnvSet = fn } } // WithAllowExecutors restricts which executors are available. func WithAllowExecutors(names []string) Option { @@ -185,10 +191,10 @@ Examples: }, { Name: "call_tool", - Description: `Run one or more named scripts from $OLLIE/s/$SID/t (the tools directory). + Description: `Run one or more named scripts from the tools directory. Tool names and their arguments are discoverable via: - grep -iA2 'keyword' $OLLIE/s/$SID/t/idx + grep -iA2 'keyword' $OLLIE_TOOLS_PATH/idx call_tool fans out in parallel when consecutive calls carry an ollie:parallel read annotation in their script header; write-annotated or unannotated tools run serially. @@ -245,7 +251,7 @@ Use this when you need to chain heterogeneous code and tool steps together. Each stage is one of: - {code, language} — inline code (default: bash) -- {tool, args} — named script from $OLLIE/s/$SID/t +- {tool, args} — named script from the tools directory - {elevated: true, code/tool} — run outside sandbox - {parallel: [{code/tool}...]} — fan-out within a stage; outputs concatenated, fed to next stage @@ -373,87 +379,15 @@ func (e *Server) SetEnv(key, value string) { e.envExtra = make(map[string]string) } e.envExtra[key] = value - // Start mount once we have the principal. - if key == "OLLIE_UNAME" && value != "" { - e.startMount(value) - } e.envMu.Unlock() -} - -// MountPath returns the per-session FUSE mount path for a given uname. -func MountPath(uname string) string { - return filepath.Join(os.TempDir(), "ollie-mount-"+uname) -} - -// startMount launches ollie-9p-mount for this session. Must be called with envMu held. -func (e *Server) startMount(sessionID string) { - if !e.mountEnabled { - return - } - bin, err := exec.LookPath("ollie-9p-mount") - if err != nil { - return - } - mnt := MountPath(sessionID) - os.MkdirAll(mnt, 0755) //nolint:errcheck - cmd := exec.Command(bin, "-u", sessionID, mnt) - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - if err := cmd.Start(); err != nil { - return - } - // Reap in background so Wait works later. - go cmd.Wait() //nolint:errcheck - // Wait for mount to be ready. - for i := 0; i < 20; i++ { - time.Sleep(50 * time.Millisecond) - if _, err := os.Stat(filepath.Join(mnt, "s")); err == nil { - break - } - } - e.mountProc = cmd.Process - e.mountPath = mnt - e.envExtra["OLLIE"] = mnt -} - -// remountIfStale checks the per-session FUSE mount health and re-establishes it if broken. -// Must be called without envMu held. -func (e *Server) remountIfStale() { - e.envMu.RLock() - mnt := e.mountPath - proc := e.mountProc - e.envMu.RUnlock() - if mnt == "" || proc == nil { - return - } - if _, err := os.Stat(filepath.Join(mnt, "s")); err == nil { - return - } - // Mount is stale — tear down and restart. - e.envMu.Lock() - defer e.envMu.Unlock() - exec.Command("fusermount", "-u", e.mountPath).Run() //nolint:errcheck - syscall.Kill(e.mountProc.Pid, syscall.SIGKILL) //nolint:errcheck - e.mountProc = nil - e.mountPath = "" - if uname := e.envExtra["OLLIE_UNAME"]; uname != "" { - e.startMount(uname) + if e.OnEnvSet != nil { + e.OnEnvSet(key, value) } } - -// Close tears down the per-session FUSE mount. +// Close is called when the session ends. Calls OnClose hook if registered. func (e *Server) Close() { - e.envMu.Lock() - proc := e.mountProc - mnt := e.mountPath - e.mountProc = nil - e.mountPath = "" - e.envMu.Unlock() - if proc != nil { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - exec.CommandContext(ctx, "fusermount", "-u", mnt).Run() //nolint:errcheck - cancel() - syscall.Kill(proc.Pid, syscall.SIGKILL) //nolint:errcheck - os.Remove(mnt) //nolint:errcheck + if e.OnClose != nil { + e.OnClose() } }