From 63c4cd61733d9015f3206c69dee92b6e914fc1d6 Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Sat, 8 Aug 2026 14:52:10 +0200 Subject: [PATCH] all: kill dead code, remove classifier system, clean up names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dead code removed: - agent: firstSentence, Checkpoint, WaitForChange, InitCond, cfgDir, execServer, SaveTo, SaveFull, saveTo, saveToFull, sanitizeMessages, LoadPersistedAgent, RestoreHistory, Restore, PersistedAgent type - toolsrv: entire tier.go (MemoryTier, MemoryTierArgs, OutputFormat, CanParallelize on Server), same methods on Conn - cmd/ollie-remote: can_parallelize, memory_tier, memory_tier_args RPC handlers - toolsrv/rpc_integration_test: all classifier tests - fs/lifecycle: state(root) dead helper Renames: - session.Session receiver: 'a' → 's' throughout - SaveSession(path) → Save() (dead parameter removed) - execServer() eliminated — callers use ag.runtime.ToolServer directly The agent-package save/restore chain was entirely dead: PersistSession in session/persist.go reads agent accessors directly and never called SaveTo/SaveFull. --- agent/agent.go | 57 +----- agent/history.go | 139 ------------- agent/models.go | 15 +- agent/turn.go | 13 -- cmd/ollie-remote/main.go | 34 ---- fs/handlers.go | 2 +- fs/lifecycle.go | 4 - session/session.go | 343 ++++++++++++++++---------------- toolsrv/conn.go | 36 ---- toolsrv/rpc_integration_test.go | 117 ----------- toolsrv/tier.go | 64 ------ 11 files changed, 181 insertions(+), 643 deletions(-) delete mode 100644 toolsrv/tier.go diff --git a/agent/agent.go b/agent/agent.go index 36780d3..ad48885 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -224,11 +224,6 @@ func (ag *Agent) WaitChange(ctx context.Context, field, current string) (string, } } -// InitCond initializes the changeCond. Must be called once after construction. -func (ag *Agent) InitCond() { - ag.changeCond = sync.NewCond(&ag.changeMu) -} - // emit sends an event to the agent's output handler. func (ag *Agent) emit(ev Event) { if ag.output != nil { @@ -311,19 +306,6 @@ func (ag *Agent) HasHistory() bool { return ag.history != nil } -// SaveTo saves the current history to the given path. -func (ag *Agent) SaveTo(path, name, cwd string) error { - if ag.history == nil { - return fmt.Errorf("no active session") - } - return ag.history.saveTo(path, name, ag.Name(), cwd) -} - -// Restore restores agent history from a persisted session. -func (ag *Agent) Restore(ps *PersistedAgent) { - ag.history = RestoreHistory(ps) -} - // ToolCallCount returns the total number of tool calls executed. func (ag *Agent) ToolCallCount() int64 { return ag.toolCallCount.Load() @@ -511,19 +493,6 @@ func (ag *Agent) toolsNeedRefresh() bool { return false } -// SaveFull persists the full session state (history + metadata) to the given path. -func (ag *Agent) SaveFull(path, sessionID, sessionName, cwd, remote string) error { - // Switching profiles intentionally clears the conversation history, but - // the profile and display name still need to be persisted. Save an empty - // history in that case rather than dropping the entire metadata snapshot. - history := ag.history - if history == nil { - history = &History{} - } - return history.saveToFull(path, sessionID, sessionName, ag.id, ag.Name(), ag.profile, - ag.runtime.Backend.Name(), ag.runtime.Backend.Model(), cwd, remote) -} - // CtxSz returns a human-readable context size string. func (ag *Agent) CtxSz() string { if ag.history == nil { @@ -606,15 +575,10 @@ func (ag *Agent) ListModels() []string { return ag.runtime.Backend.Models(context.Background()) } -// execServer returns the execute server, or nil if unavailable. -func (ag *Agent) execServer() toolsrv.Runner { - return ag.runtime.ToolServer -} - // ToolServer returns the tool execution server, or nil if unavailable. // Exported for use by the 9P filesystem layer to sync tool registries. func (ag *Agent) ToolServer() toolsrv.Runner { - return ag.execServer() + return ag.runtime.ToolServer } // Queue pushes a prompt onto the agent's FIFO. @@ -634,18 +598,9 @@ func (ag *Agent) BroadcastChange() { ag.changeMu.Unlock() } -// WaitForChange blocks until a state change is broadcast or ctx is cancelled. -func (ag *Agent) WaitForChange(ctx context.Context) { - ag.changeMu.Lock() - if ctx.Err() == nil { - ag.changeCond.Wait() - } - ag.changeMu.Unlock() -} - // Detach detaches the current running process to background. func (ag *Agent) Detach() bool { - if srv := ag.execServer(); srv != nil { + if srv := ag.runtime.ToolServer; srv != nil { if d, ok := srv.(interface{ Detach() bool }); ok { return d.Detach() } @@ -664,7 +619,7 @@ type DetachedInfo struct { // ListDetached returns info about all detached processes. func (ag *Agent) ListDetached() []DetachedInfo { - srv := ag.execServer() + srv := ag.runtime.ToolServer if srv == nil { return nil } @@ -701,7 +656,7 @@ func (ag *Agent) ListDetached() []DetachedInfo { // SignalDetached sends a signal to a detached process. func (ag *Agent) SignalDetached(pid int, signal int) error { - if srv := ag.execServer(); srv != nil { + if srv := ag.runtime.ToolServer; srv != nil { type signaler interface { SignalDetached(int, syscall.Signal) error } @@ -714,7 +669,7 @@ func (ag *Agent) SignalDetached(pid int, signal int) error { // GetDetachedOutput reads output from a detached process. func (ag *Agent) GetDetachedOutput(pid int) (string, error) { - if srv := ag.execServer(); srv != nil { + if srv := ag.runtime.ToolServer; srv != nil { type outputGetter interface{ GetDetachedOutput(int) (string, error) } if og, ok := srv.(outputGetter); ok { return og.GetDetachedOutput(pid) @@ -725,7 +680,7 @@ func (ag *Agent) GetDetachedOutput(pid int) (string, error) { // DismissDetached removes a finished detached process. func (ag *Agent) DismissDetached(pid int) bool { - if srv := ag.execServer(); srv != nil { + if srv := ag.runtime.ToolServer; srv != nil { type dismisser interface{ DismissDetached(int) bool } if d, ok := srv.(dismisser); ok { return d.DismissDetached(pid) diff --git a/agent/history.go b/agent/history.go index af424f3..2f41c07 100644 --- a/agent/history.go +++ b/agent/history.go @@ -5,7 +5,6 @@ import ( "crypto/rand" "encoding/json" "fmt" - "os" "strings" "ollie/backend" @@ -33,31 +32,6 @@ Be concise. Capture what another LLM needs to seamlessly continue.` warmIndexSize = 10 ) -// PersistedAgent is the on-disk format for a saved session. -type PersistedAgent struct { - ID string `json:"id"` - // SessionName is the mutable display name of the containing session. - SessionName string `json:"sessionName,omitempty"` - Uname string `json:"uname,omitempty"` // agent's immutable 9P identity - Agent string `json:"agent,omitempty"` - Profile string `json:"profile,omitempty"` - Backend string `json:"backend,omitempty"` - Model string `json:"model,omitempty"` - CWD string `json:"cwd,omitempty"` - Remote string `json:"remote,omitempty"` - Messages []backend.Message `json:"messages"` - TaskState *TaskState `json:"taskState,omitempty"` - // Usage and cost tracking, persisted across restarts. - TotalInputTokens int `json:"totalInputTokens,omitempty"` - TotalCachedInputTokens int `json:"totalCachedInputTokens,omitempty"` - TotalCacheCreationTokens int `json:"totalCacheCreationTokens,omitempty"` - TotalOutputTokens int `json:"totalOutputTokens,omitempty"` - TotalRequests int `json:"totalRequests,omitempty"` - Estimated bool `json:"estimated,omitempty"` - LastTurnCostUSD float64 `json:"lastTurnCostUSD,omitempty"` - SessionCostUSD float64 `json:"sessionCostUSD,omitempty"` -} - // TaskState is a compact structured overlay that summarizes the agent's // current position in a task. Injected at the top of every turn so the // model doesn't rely on full history recall. @@ -97,104 +71,6 @@ func (ts *TaskState) render() string { return sb.String() } -// saveTo writes the full message history to path as JSON. -func (s *History) saveTo(path, id, displayName, cwd string) error { - return s.saveToFull(path, id, "", "", displayName, "", "", "", cwd, "") -} - -// saveToFull writes session state including backend/model info. -func sanitizeMessages(msgs []backend.Message) []backend.Message { - out := make([]backend.Message, 0, len(msgs)) - for _, m := range msgs { - for j, tc := range m.ToolCalls { - if len(tc.Arguments) > 0 { - var dummy json.RawMessage - if err := json.Unmarshal(tc.Arguments, &dummy); err != nil { - // Corrupted arguments — replace with empty object so save succeeds. - m.ToolCalls[j].Arguments = json.RawMessage("{}") - } - } - } - out = append(out, m) - } - return out -} - -func (s *History) saveToFull(path, id, sessionName, uname, displayName, profile, backendName, modelName, cwd, remote string) error { - ps := PersistedAgent{ - ID: id, - SessionName: sessionName, - Uname: uname, - Agent: displayName, - Profile: profile, - Backend: backendName, - Model: modelName, - CWD: cwd, - Remote: remote, - Messages: sanitizeMessages(s.messages), - TaskState: s.TaskState, - TotalInputTokens: s.TotalInputTokens, - TotalCachedInputTokens: s.TotalCachedInputTokens, - TotalCacheCreationTokens: s.TotalCacheCreationTokens, - TotalOutputTokens: s.TotalOutputTokens, - TotalRequests: s.TotalRequests, - Estimated: s.Estimated, - LastTurnCostUSD: s.LastTurnCostUSD, - SessionCostUSD: s.SessionCostUSD, - } - data, err := json.Marshal(ps) - if err != nil { - return fmt.Errorf("session save: %w", err) - } - // Atomic write: write to temp file, then rename to preserve last good snapshot. - tmpPath := path + ".tmp" - if err := os.WriteFile(tmpPath, data, 0600); err != nil { - return fmt.Errorf("session save tmp: %w", err) - } - return os.Rename(tmpPath, path) -} - -// LoadPersistedAgent reads a PersistedAgent from a JSON file. -func LoadPersistedAgent(path string) (*PersistedAgent, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - var ps PersistedAgent - if err := json.Unmarshal(data, &ps); err != nil { - return nil, fmt.Errorf("parse session %s: %w", path, err) - } - return &ps, nil -} - -// RestoreHistory reconstructs a Session from a persisted message list. -func RestoreHistory(ps *PersistedAgent) *History { - s := &History{ - messages: ps.Messages, - TaskState: ps.TaskState, - TotalInputTokens: ps.TotalInputTokens, - TotalCachedInputTokens: ps.TotalCachedInputTokens, - TotalCacheCreationTokens: ps.TotalCacheCreationTokens, - TotalOutputTokens: ps.TotalOutputTokens, - TotalRequests: ps.TotalRequests, - Estimated: ps.Estimated, - LastTurnCostUSD: ps.LastTurnCostUSD, - SessionCostUSD: ps.SessionCostUSD, - } - for i := range s.messages { - if s.messages[i].Role == "assistant" && s.messages[i].ID == "" { - s.messages[i].ID = NewResponseID() - } - } - for _, m := range ps.Messages { - if m.Role == "user" { - s.goal = m.Content - break - } - } - return s -} - // RestoreHistoryFromMessages reconstructs a History from just the message list. // Used for multi-agent session restore where each agent has separate messages. func RestoreHistoryFromMessages(messages []backend.Message) *History { @@ -244,21 +120,6 @@ func newHistory(goal string) *History { } // Checkpoint forks the session: returns a new Session that inherits the given -// TaskState but starts with a clean message history. This enables narrow-context -// sub-agents that know what to do without inheriting all parent message noise. -func (s *History) Checkpoint(ts TaskState) *History { - child := &History{ - goal: ts.Objective, - TaskState: &ts, - } - // Seed with a user message so the child has a valid initial turn. - child.messages = []backend.Message{{ - Role: "user", - Content: ts.render(), - }} - return child -} - func (s *History) history() []backend.Message { return s.messages } diff --git a/agent/models.go b/agent/models.go index 148059a..34a74c7 100644 --- a/agent/models.go +++ b/agent/models.go @@ -2,7 +2,8 @@ package agent import ( "os" - "path/filepath" + + "ollie/paths" "gopkg.in/yaml.v3" ) @@ -19,8 +20,7 @@ type modelsConfig struct { // loadModelsConfig reads and parses the models config file. // Returns zero value if the file doesn't exist or is invalid. func loadModelsConfig() modelsConfig { - path := filepath.Join(cfgDir(), "models.yaml") - data, err := os.ReadFile(path) + data, err := os.ReadFile(paths.CfgDir() + "/models.yaml") if err != nil { return modelsConfig{} } @@ -30,12 +30,3 @@ func loadModelsConfig() modelsConfig { } return cfg } - -// cfgDir returns the ollie config directory, respecting XDG_CONFIG_HOME. -func cfgDir() string { - if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" { - return filepath.Join(xdg, "ollie") - } - home, _ := os.UserHomeDir() - return filepath.Join(home, ".config", "ollie") -} diff --git a/agent/turn.go b/agent/turn.go index 029681a..21b17c5 100644 --- a/agent/turn.go +++ b/agent/turn.go @@ -389,16 +389,3 @@ func infoEvent(text string) Event { return Event{Role: "info", Content: text + "\n"} } -// firstSentence returns the first sentence of s (up to the first period or -// newline), trimmed. Falls back to s truncated at 80 chars if no sentence end found. -func firstSentence(s string) string { - for i, r := range s { - if r == '.' || r == '\n' { - return strings.TrimSpace(s[:i+1]) - } - } - if len(s) > 80 { - return s[:77] + "..." - } - return s -} diff --git a/cmd/ollie-remote/main.go b/cmd/ollie-remote/main.go index c367430..c96943f 100644 --- a/cmd/ollie-remote/main.go +++ b/cmd/ollie-remote/main.go @@ -334,40 +334,6 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo ok := srv.DismissDetached(params.PID) enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: ok}) - case "can_parallelize": - var params struct { - Name string `json:"name"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}}) - return - } - ok := srv.CanParallelize(params.Name) - enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: ok}) - - case "memory_tier": - var params struct { - Name string `json:"name"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}}) - return - } - tier := srv.MemoryTier(params.Name) - enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: tier}) - - case "memory_tier_args": - var params struct { - Name string `json:"name"` - Args json.RawMessage `json:"args"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}}) - return - } - tier := srv.MemoryTierArgs(params.Name, params.Args) - enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: tier}) - case "tool_load": var params struct { Name string `json:"name"` diff --git a/fs/handlers.go b/fs/handlers.go index 4fcb99a..c4a7c55 100644 --- a/fs/handlers.go +++ b/fs/handlers.go @@ -322,7 +322,7 @@ func writeSessionCtl(ctx HandlerCtx, data []byte) error { }, "save": func(_ []string, _ HandlerCtx) error { if ctx.Session.Core != nil { - ctx.Session.Core.SaveSession("") + ctx.Session.Core.Save() } return nil }, diff --git a/fs/lifecycle.go b/fs/lifecycle.go index b73003b..f9cad42 100644 --- a/fs/lifecycle.go +++ b/fs/lifecycle.go @@ -57,10 +57,6 @@ func (rs *RootState) RenameNode(oldName, newName string) { } } -func state(root *Tree) *RootState { - return root.Data.(*RootState) -} - // Shutdown interrupts all sessions, waits for idle, persists, and closes. func Shutdown(root *Tree) { session.Shutdown() diff --git a/session/session.go b/session/session.go index 3d76321..fdb3a40 100644 --- a/session/session.go +++ b/session/session.go @@ -155,7 +155,7 @@ func New(cfg Config) *Session { name = cfg.SessionID[:idx] } - a := &Session{ + s := &Session{ id: cfg.SessionID, name: name, env: make(map[string]string), @@ -185,18 +185,18 @@ func New(cfg Config) *Session { SessionID: cfg.SessionID, StartupMsgs: rt.Messages, ReadPlanStep: cfg.ReadPlanStep, - Save: a.saveSession, - Flush: a.flushSave, + Save: s.saveSession, + Flush: s.flushSave, }) - a.agents = append(a.agents, ag) - ag.SetSessionEnv(a.id) - return a + s.agents = append(s.agents, ag) + ag.SetSessionEnv(s.id) + return s } // FindAgent returns the agent matching the given name or ID, or nil. -func (a *Session) FindAgent(nameOrID string) *agent.Agent { - for _, ag := range a.agents { +func (s *Session) FindAgent(nameOrID string) *agent.Agent { + for _, ag := range s.agents { if ag.Name() == nameOrID || ag.ID() == nameOrID { return ag } @@ -205,37 +205,37 @@ func (a *Session) FindAgent(nameOrID string) *agent.Agent { } // Agents returns the full agent slice. -func (a *Session) Agents() []*agent.Agent { return a.agents } +func (s *Session) Agents() []*agent.Agent { return s.agents } // AgentAt returns the agent at the given index, or nil. -func (a *Session) AgentAt(idx int) *agent.Agent { - if idx < 0 || idx >= len(a.agents) { +func (s *Session) AgentAt(idx int) *agent.Agent { + if idx < 0 || idx >= len(s.agents) { return nil } - return a.agents[idx] + return s.agents[idx] } // AgentCount returns the number of agents. -func (a *Session) AgentCount() int { return len(a.agents) } +func (s *Session) AgentCount() int { return len(s.agents) } // SaveFuncs returns save and flush callbacks suitable for use as // SaveFuncs returns the save and flush callbacks for use in // agent.AgentCfg.Save and Flush when adding agents to this session. -func (a *Session) SaveFuncs() (save func(), flush func()) { - return a.saveSession, a.flushSave +func (s *Session) SaveFuncs() (save func(), flush func()) { + return s.saveSession, s.flushSave } // AddAgent appends an agent to the session. -func (a *Session) AddAgent(ag *agent.Agent) { - a.agents = append(a.agents, ag) - ag.SetSessionEnv(a.id) +func (s *Session) AddAgent(ag *agent.Agent) { + s.agents = append(s.agents, ag) + ag.SetSessionEnv(s.id) } // RemoveAgent removes an agent by ID and closes it. // Returns false if the agent was not found. -func (a *Session) RemoveAgent(id string) bool { +func (s *Session) RemoveAgent(id string) bool { idx := -1 - for i, ag := range a.agents { + for i, ag := range s.agents { if ag.ID() == id { idx = i break @@ -244,61 +244,61 @@ func (a *Session) RemoveAgent(id string) bool { if idx < 0 { return false } - a.agents[idx].Close() - a.agents = append(a.agents[:idx], a.agents[idx+1:]...) + s.agents[idx].Close() + s.agents = append(s.agents[:idx], s.agents[idx+1:]...) return true } // Close releases resources for this session. -func (a *Session) Close() { - a.log.Debug("Close() session=%q", a.id) - a.flushSave() - for _, ag := range a.agents { +func (s *Session) Close() { + s.log.Debug("Close() session=%q", s.id) + s.flushSave() + for _, ag := range s.agents { ag.Close() } - if a.id != "" { - os.RemoveAll(filepath.Join(ollieTmpDir(), a.id)) //nolint:errcheck + if s.id != "" { + os.RemoveAll(filepath.Join(ollieTmpDir(), s.id)) //nolint:errcheck } } // SetEnv stores a session-scoped variable and propagates to all agents. -func (a *Session) SetEnv(key, value string) { - a.envMu.Lock() - a.env[key] = value - a.envMu.Unlock() - for _, ag := range a.agents { +func (s *Session) SetEnv(key, value string) { + s.envMu.Lock() + s.env[key] = value + s.envMu.Unlock() + for _, ag := range s.agents { ag.SetEnv(key, value) } } // Remote returns the remote target (e.g., SSH host) if any. -func (a *Session) Remote() string { - return a.remote +func (s *Session) Remote() string { + return s.remote } // SetRemote sets the remote target for this session. -func (a *Session) SetRemote(remote string) { - a.remote = remote +func (s *Session) SetRemote(remote string) { + s.remote = remote } // SetSessionID renames the session. -func (a *Session) SetSessionID(newID string) error { - oldID := a.id +func (s *Session) SetSessionID(newID string) error { + oldID := s.id if oldID == newID { return nil } - if a.sessionsDir != "" && oldID != "" { + if s.sessionsDir != "" && oldID != "" { for _, suffix := range []string{".json", ".compaction.jsonl"} { - oldPath := a.activeSessionPath(oldID, suffix) + oldPath := s.activeSessionPath(oldID, suffix) if _, err := os.Stat(oldPath); err == nil { - if err := os.Rename(oldPath, a.activeSessionPath(newID, suffix)); err != nil { + if err := os.Rename(oldPath, s.activeSessionPath(newID, suffix)); err != nil { return fmt.Errorf("rename %s: %w", suffix, err) } } } } - a.id = newID - for _, ag := range a.agents { + s.id = newID + for _, ag := range s.agents { ag.RenamePreamble(oldID, newID) } oldTemp := filepath.Join(ollieTmpDir(), oldID) @@ -306,161 +306,160 @@ func (a *Session) SetSessionID(newID string) error { if _, err := os.Stat(oldTemp); err == nil { os.Rename(oldTemp, newTemp) //nolint:errcheck } - for _, ag := range a.agents { + for _, ag := range s.agents { ag.SetSessionEnv(newID) } return nil } -func (a *Session) activeSessionPath(id, suffix string) string { - return filepath.Join(a.sessionsDir, "active", id+suffix) +func (s *Session) activeSessionPath(id, suffix string) string { + return filepath.Join(s.sessionsDir, "active", id+suffix) } -func (a *Session) saveSession() { - a.saveMu.Lock() - a.saveDirty = true - if a.saveTimer == nil { - a.saveTimer = time.AfterFunc(2*time.Second, a.flushSave) +func (s *Session) saveSession() { + s.saveMu.Lock() + s.saveDirty = true + if s.saveTimer == nil { + s.saveTimer = time.AfterFunc(2*time.Second, s.flushSave) } - a.saveMu.Unlock() + s.saveMu.Unlock() } -func (a *Session) flushSave() { - a.saveMu.Lock() - dirty := a.saveDirty - a.saveDirty = false - if a.saveTimer != nil { - a.saveTimer.Stop() - a.saveTimer = nil +func (s *Session) flushSave() { + s.saveMu.Lock() + dirty := s.saveDirty + s.saveDirty = false + if s.saveTimer != nil { + s.saveTimer.Stop() + s.saveTimer = nil } - a.saveMu.Unlock() - if !dirty || a.id == "" || a.sessionsDir == "" { + s.saveMu.Unlock() + if !dirty || s.id == "" || s.sessionsDir == "" { return } // Use PersistSession for consistent format (writes Name field correctly) - if err := PersistSession(a.Name()); err != nil { - a.log.Error("session save: %v", err) + if err := PersistSession(s.Name()); err != nil { + s.log.Error("session save: %v", err) } } -// SaveSession writes the current session state. -// The path argument is ignored; session is saved via PersistSession. -func (a *Session) SaveSession(path string) error { - return PersistSession(a.Name()) +// Save writes the current session state via PersistSession. +func (s *Session) Save() error { + return PersistSession(s.Name()) } // --- Identity accessors --- // ID returns the immutable session UUID. -func (a *Session) ID() string { return a.id } +func (s *Session) ID() string { return s.id } // Name returns the mutable friendly name. -func (a *Session) Name() string { - a.mu.RLock() - defer a.mu.RUnlock() - return a.name +func (s *Session) Name() string { + s.mu.RLock() + defer s.mu.RUnlock() + return s.name } // SetName sets the friendly name. -func (a *Session) SetName(name string) { - a.mu.Lock() - defer a.mu.Unlock() - a.name = name +func (s *Session) SetName(name string) { + s.mu.Lock() + defer s.mu.Unlock() + s.name = name } // Uname returns the immutable user principal. -func (a *Session) Uname() string { return a.uname } +func (s *Session) Uname() string { return s.uname } // SetUname sets the user principal (should only be called during init). -func (a *Session) SetUname(uname string) { - a.mu.Lock() - defer a.mu.Unlock() - a.uname = uname +func (s *Session) SetUname(uname string) { + s.mu.Lock() + defer s.mu.Unlock() + s.uname = uname } // --- Context lifecycle --- // Ctx returns the session context. -func (a *Session) Ctx() context.Context { return a.ctx } +func (s *Session) Ctx() context.Context { return s.ctx } // Cancel cancels the session context. -func (a *Session) Cancel() { - if a.cancel != nil { - a.cancel() +func (s *Session) Cancel() { + if s.cancel != nil { + s.cancel() } } // SetContext sets the session context and cancel func. -func (a *Session) SetContext(ctx context.Context, cancel context.CancelFunc) { - a.ctx = ctx - a.cancel = cancel +func (s *Session) SetContext(ctx context.Context, cancel context.CancelFunc) { + s.ctx = ctx + s.cancel = cancel } // --- Tool server lifecycle --- // Proc returns the tool server process. -func (a *Session) Proc() *toolsrv.Process { - a.mu.RLock() - defer a.mu.RUnlock() - return a.proc +func (s *Session) Proc() *toolsrv.Process { + s.mu.RLock() + defer s.mu.RUnlock() + return s.proc } // SetProc sets the tool server process. -func (a *Session) SetProc(proc *toolsrv.Process) { - a.mu.Lock() - defer a.mu.Unlock() - a.proc = proc +func (s *Session) SetProc(proc *toolsrv.Process) { + s.mu.Lock() + defer s.mu.Unlock() + s.proc = proc } // Keeper returns the process keeper. -func (a *Session) Keeper() *toolsrv.ProcessKeeper { - a.mu.RLock() - defer a.mu.RUnlock() - return a.keeper +func (s *Session) Keeper() *toolsrv.ProcessKeeper { + s.mu.RLock() + defer s.mu.RUnlock() + return s.keeper } // SetKeeper sets the process keeper. -func (a *Session) SetKeeper(keeper *toolsrv.ProcessKeeper) { - a.mu.Lock() - defer a.mu.Unlock() - a.keeper = keeper +func (s *Session) SetKeeper(keeper *toolsrv.ProcessKeeper) { + s.mu.Lock() + defer s.mu.Unlock() + s.keeper = keeper } // ToolsConn returns the tool server connection. -func (a *Session) ToolsConn() toolsrv.Runner { - a.mu.RLock() - defer a.mu.RUnlock() - return a.toolsConn +func (s *Session) ToolsConn() toolsrv.Runner { + s.mu.RLock() + defer s.mu.RUnlock() + return s.toolsConn } // SetToolsConn sets the tool server connection. -func (a *Session) SetToolsConn(conn toolsrv.Runner) { - a.mu.Lock() - defer a.mu.Unlock() - a.toolsConn = conn +func (s *Session) SetToolsConn(conn toolsrv.Runner) { + s.mu.Lock() + defer s.mu.Unlock() + s.toolsConn = conn } // IsPaused returns true if the session is paused. -func (a *Session) IsPaused() bool { - a.mu.RLock() - defer a.mu.RUnlock() - return a.paused +func (s *Session) IsPaused() bool { + s.mu.RLock() + defer s.mu.RUnlock() + return s.paused } // SetPaused sets the paused state directly (used during restore). -func (a *Session) SetPaused(paused bool) { - a.mu.Lock() - defer a.mu.Unlock() - a.paused = paused +func (s *Session) SetPaused(paused bool) { + s.mu.Lock() + defer s.mu.Unlock() + s.paused = paused } // IsConnected returns true if the tool server connection is alive. // Returns false if the session is paused. -func (a *Session) IsConnected() bool { - a.mu.RLock() - paused := a.paused - conn := a.toolsConn - a.mu.RUnlock() +func (s *Session) IsConnected() bool { + s.mu.RLock() + paused := s.paused + conn := s.toolsConn + s.mu.RUnlock() if paused || conn == nil { return false } @@ -473,61 +472,61 @@ func (a *Session) IsConnected() bool { // Pause stops the tool server to save resources. // It cancels the session context (stopping all agent operations), // kills the tool server process, and marks the session as paused. -func (a *Session) Pause() error { - a.mu.Lock() - defer a.mu.Unlock() - if a.paused { +func (s *Session) Pause() error { + s.mu.Lock() + defer s.mu.Unlock() + if s.paused { return fmt.Errorf("session already paused") } // Cancel the session context to stop all agent operations. - if a.cancel != nil { - a.cancel() + if s.cancel != nil { + s.cancel() } // Close the tool server process. - if a.keeper != nil { - a.keeper.Close() - } else if a.proc != nil { - a.proc.Close() + if s.keeper != nil { + s.keeper.Close() + } else if s.proc != nil { + s.proc.Close() } // Close any existing connection. - if a.toolsConn != nil { - if c, ok := a.toolsConn.(interface{ Close() }); ok { + if s.toolsConn != nil { + if c, ok := s.toolsConn.(interface{ Close() }); ok { c.Close() } - a.toolsConn = nil + s.toolsConn = nil } - a.paused = true + s.paused = true // Persist the paused state. - go a.saveSession() + go s.saveSession() return nil } // Resume restarts the tool server after a Pause. // It creates a fresh session context and respawns the tool server. // For sessions restored in paused state (no keeper), it sets up the tool server from scratch. -func (a *Session) Resume() error { - a.mu.Lock() - defer a.mu.Unlock() - if !a.paused { +func (s *Session) Resume() error { + s.mu.Lock() + defer s.mu.Unlock() + if !s.paused { return fmt.Errorf("session not paused") } // Create a fresh context since the old one was cancelled on pause. ctx, cancel := context.WithCancel(serverCtx) - a.ctx = ctx - a.cancel = cancel + s.ctx = ctx + s.cancel = cancel // If we have a keeper, use it to respawn. Otherwise set up from scratch. - if a.keeper != nil { - a.keeper.SetContext(ctx) - conn, err := a.keeper.Dial() + if s.keeper != nil { + s.keeper.SetContext(ctx) + conn, err := s.keeper.Dial() if err != nil { return fmt.Errorf("resume failed: %w", err) } - a.toolsConn = conn + s.toolsConn = conn } else { // Session was restored paused without infra - set up per-agent. - for i, ag := range a.agents { + for i, ag := range s.agents { cwd := ag.Cwd() if cwd == "" { cwd, _ = os.Getwd() @@ -535,18 +534,18 @@ func (a *Session) Resume() error { // First agent spawns the tool server, others reuse var reuseFrom *InfraConfig - if a.proc != nil { + if s.proc != nil { reuseFrom = &InfraConfig{ - Proc: a.proc, - Keeper: a.keeper, - ToolsConn: a.toolsConn, + Proc: s.proc, + Keeper: s.keeper, + ToolsConn: s.toolsConn, } } infra, err := SetupToolServer(ToolServerConfig{ Ctx: ctx, CWD: cwd, - RemoteTarget: a.remote, + RemoteTarget: s.remote, Yolo: pkgYolo, ReuseFrom: reuseFrom, }) @@ -556,45 +555,45 @@ func (a *Session) Resume() error { // First agent sets session infra if i == 0 { - a.proc = infra.Proc - a.keeper = infra.Keeper - a.toolsConn = infra.ToolsConn + s.proc = infra.Proc + s.keeper = infra.Keeper + s.toolsConn = infra.ToolsConn } ag.SetToolServer(infra.NewToolServer, infra.ToolsConn) } } - a.paused = false + s.paused = false // Persist the resumed state. - go a.saveSession() + go s.saveSession() return nil } // --- Tool loading --- // SetDisallowTools sets the tool disallow list. -func (a *Session) SetDisallowTools(disallow map[string]struct{}) { - a.mu.Lock() - defer a.mu.Unlock() - a.disallowTools = disallow +func (s *Session) SetDisallowTools(disallow map[string]struct{}) { + s.mu.Lock() + defer s.mu.Unlock() + s.disallowTools = disallow } // LoadTool loads a tool into the tool server. -func (a *Session) LoadTool(name string, ag *agent.Agent) error { +func (s *Session) LoadTool(name string, ag *agent.Agent) error { name = strings.TrimSpace(name) if name == "" { return nil } - a.mu.RLock() - _, blocked := a.disallowTools[name] - a.mu.RUnlock() + s.mu.RLock() + _, blocked := s.disallowTools[name] + s.mu.RUnlock() if blocked { return fmt.Errorf("tool %q is disallowed for this session", name) } var runner toolsrv.Runner - if a.toolsConn != nil { - runner = a.toolsConn + if s.toolsConn != nil { + runner = s.toolsConn } else if ag != nil { runner = ag.ToolServer() } diff --git a/toolsrv/conn.go b/toolsrv/conn.go index 8d89c64..969ac07 100644 --- a/toolsrv/conn.go +++ b/toolsrv/conn.go @@ -151,42 +151,6 @@ func (c *Conn) DismissDetached(pid int) bool { return ok } -// CanParallelize implements ParallelClassifier. -func (c *Conn) CanParallelize(name string) bool { - params, _ := json.Marshal(map[string]string{"name": name}) - resp, err := c.call("can_parallelize", params) - if err != nil { - return false - } - var ok bool - json.Unmarshal(resp, &ok) - return ok -} - -// MemoryTier implements MemoryTierClassifier. -func (c *Conn) MemoryTier(name string) string { - params, _ := json.Marshal(map[string]string{"name": name}) - resp, err := c.call("memory_tier", params) - if err != nil { - return "hot" - } - var tier string - json.Unmarshal(resp, &tier) - return tier -} - -// MemoryTierArgs implements MemoryTierClassifier. -func (c *Conn) MemoryTierArgs(name string, args json.RawMessage) string { - params, _ := json.Marshal(map[string]any{"name": name, "args": args}) - resp, err := c.call("memory_tier_args", params) - if err != nil { - return "hot" - } - var tier string - json.Unmarshal(resp, &tier) - return tier -} - // Close shuts down the connection and subprocess. func (c *Conn) Close() { c.fail(fmt.Errorf("connection closed")) diff --git a/toolsrv/rpc_integration_test.go b/toolsrv/rpc_integration_test.go index c11f060..b8b8ce7 100644 --- a/toolsrv/rpc_integration_test.go +++ b/toolsrv/rpc_integration_test.go @@ -130,40 +130,6 @@ func (ts *testServer) handleRPC(ctx context.Context, req rpcRequest) rpcResponse ts.srv.SetAllowTools(params.Names) return rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(`true`)} - case "can_parallelize": - var params struct { - Name string `json:"name"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - return rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}} - } - ok := ts.srv.CanParallelize(params.Name) - result, _ := json.Marshal(ok) - return rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: result} - - case "memory_tier": - var params struct { - Name string `json:"name"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - return rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}} - } - tier := ts.srv.MemoryTier(params.Name) - result, _ := json.Marshal(tier) - return rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: result} - - case "memory_tier_args": - var params struct { - Name string `json:"name"` - Args json.RawMessage `json:"args"` - } - if err := json.Unmarshal(req.Params, ¶ms); err != nil { - return rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}} - } - tier := ts.srv.MemoryTierArgs(params.Name, params.Args) - result, _ := json.Marshal(tier) - return rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: result} - case "detach": ok := ts.srv.Detach() result, _ := json.Marshal(ok) @@ -449,78 +415,6 @@ func TestRPC_SetAllowTools(t *testing.T) { } } -func TestRPC_CanParallelize(t *testing.T) { - ts := newTestServer(t) - defer ts.srv.Close() - - ctx := context.Background() - params, _ := json.Marshal(map[string]string{"name": "unknown_tool"}) - req := rpcRequest{JSONRPC: "2.0", ID: 1, Method: "can_parallelize", Params: params} - resp := ts.handleRPC(ctx, req) - - if resp.Error != nil { - t.Fatalf("unexpected error: %v", resp.Error.Message) - } - - var result bool - if err := json.Unmarshal(resp.Result, &result); err != nil { - t.Fatalf("unmarshal result: %v", err) - } - // Unknown tools default to not parallel-read-safe - if result { - t.Error("expected false for unknown tool") - } -} - -func TestRPC_MemoryTier(t *testing.T) { - ts := newTestServer(t) - defer ts.srv.Close() - - ctx := context.Background() - params, _ := json.Marshal(map[string]string{"name": "unknown_tool"}) - req := rpcRequest{JSONRPC: "2.0", ID: 1, Method: "memory_tier", Params: params} - resp := ts.handleRPC(ctx, req) - - if resp.Error != nil { - t.Fatalf("unexpected error: %v", resp.Error.Message) - } - - var tier string - if err := json.Unmarshal(resp.Result, &tier); err != nil { - t.Fatalf("unmarshal result: %v", err) - } - // Unknown tools default to "hot" - if tier != "hot" { - t.Errorf("expected tier='hot', got %q", tier) - } -} - -func TestRPC_MemoryTierArgs(t *testing.T) { - ts := newTestServer(t) - defer ts.srv.Close() - - ctx := context.Background() - params, _ := json.Marshal(map[string]any{ - "name": "some_tool", - "args": json.RawMessage(`{"foo": "bar"}`), - }) - req := rpcRequest{JSONRPC: "2.0", ID: 1, Method: "memory_tier_args", Params: params} - resp := ts.handleRPC(ctx, req) - - if resp.Error != nil { - t.Fatalf("unexpected error: %v", resp.Error.Message) - } - - var tier string - if err := json.Unmarshal(resp.Result, &tier); err != nil { - t.Fatalf("unmarshal result: %v", err) - } - // Unknown tools default to "hot" - if tier != "hot" { - t.Errorf("expected tier='hot', got %q", tier) - } -} - func TestRPC_Detach_NoRunningProcess(t *testing.T) { ts := newTestServer(t) defer ts.srv.Close() @@ -700,17 +594,6 @@ func TestRPC_PipeConnection(t *testing.T) { // Test set cwd conn.SetCWD("/tmp/test") - // Test is_parallel_read - if conn.CanParallelize("unknown") { - t.Error("expected CanParallelize=false for unknown tool") - } - - // Test result_tier - tier := conn.MemoryTier("unknown") - if tier != "hot" { - t.Errorf("expected tier='hot', got %q", tier) - } - // Clean up cancel() conn.Close() diff --git a/toolsrv/tier.go b/toolsrv/tier.go deleted file mode 100644 index 3ffea35..0000000 --- a/toolsrv/tier.go +++ /dev/null @@ -1,64 +0,0 @@ -package toolsrv - -import ( - "encoding/json" -) - -// MemoryTier implements MemoryTierClassifier. Looks up the tool's tier -// from the registry, then from its .meta file. -func (e *Server) MemoryTier(name string) string { - if e.toolRegistry != nil && e.sessionID != "" { - if info, ok := e.toolRegistry.Lookup(e.sessionID, name); ok && info.Tier != "" { - return info.Tier - } - } - m, err := LoadMetaFile(name) - if err == nil && m != nil && m.Tier != "" { - return m.Tier - } - return "hot" -} - -// OutputFormat returns the source-fence language for a tool's output. -// Empty means the output should be wrapped in a plaintext fence. -func (e *Server) OutputFormat(name string) string { - if e.toolRegistry != nil && e.sessionID != "" { - if info, ok := e.toolRegistry.Lookup(e.sessionID, name); ok { - return info.OutputFormat - } - } - m, err := LoadMetaFile(name) - if err == nil && m != nil { - if resolved := m.Resolve(); resolved != nil { - return resolved.OutputFormat - } - } - return "" -} - -// MemoryTierArgs classifies the tier using both the outer tool name and its -// arguments. For shell, it returns warm. For promoted tools, it -// delegates to MemoryTier. -func (e *Server) MemoryTierArgs(name string, args json.RawMessage) string { - switch name { - case "shell": - return "warm" - default: - return e.MemoryTier(name) - } -} - -// CanParallelize implements ParallelClassifier. Returns true when the -// named tool is marked readOnly in its .meta file. -func (e *Server) CanParallelize(name string) bool { - if e.toolRegistry != nil && e.sessionID != "" { - if info, ok := e.toolRegistry.Lookup(e.sessionID, name); ok { - return info.ReadOnly - } - } - m, err := LoadMetaFile(name) - if err == nil && m != nil { - return m.ReadOnly - } - return false -}