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.
This commit is contained in:
Levi Neely 2026-07-08 15:55:10 +02:00
parent f05b736af9
commit fd20070294
3 changed files with 24 additions and 89 deletions

View File

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

View File

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

View File

@ -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()
}
}