New files:
- fs9p.go: FS9P struct with /proc-based execution model
- /proc/new (rdwr): blocking tool execution
- /proc/new.bg: background/detached execution
- /proc/<pid>/out, wait, stat, ctl: process management
- /tools, /ctl, /info: tool registry and host info
- Payload format: key=value lines, newline delimited
- exec9p.go: Decoupled tool execution
- ExecuteTool: standalone function for tool execution
- executeSandboxed: sandbox execution without Server deps
- executeBypassDirect: bypass broker without Server deps
- fs9p_test.go: Unit tests for FS9P
Design notes:
- Background execution is the internal model
- Foreground (/proc/new rdwr) blocks until completion
- No per-connection state (cwd/env passed per-call or at attach)
- Session ID scoping via registry, not env propagation
This is the foundation - actual 9P serving and integration pending.
- doc/boot-sequence.md: init chain from main → fs.NewRoot → session.Init
- Event protocol: comprehensive table on Event struct (all roles, semantics)
- RPC server moved from cmd/ollie-remote into toolsrv/rpcserver.go
(binary is now a 93-line thin wrapper)
- SetSessionEnv folded into NewAgent (no post-construction wiring needed)
- AgentCfg → AgentParams (distinguish from AgentConfig JSON schema)
- ToolResult/ToolResultContent shared types in toolsrv/rpcwire.go
- toolsrv/shell.go → toolsrv/exec.go (name matches purpose)
- Resume unified: single path, always through Keeper
- AGENTS.md: fix fs/builder.go → fsedsl/builder.go, handlers_*.go
- fsedsl/Tree: document dual-mode (EDSL lazy vs Manual Mount)
- Truncation stack documented at defaultToolResultMaxBytes
- Integration test rewritten to use srv.ServeRPC directly
session.New() had zero callers — all sessions are created via
NewEmpty() + buildAgent(). Remove it along with the Config struct
(20 fields) and the per-session sessionsDir field (always identical
to pkgSessionsDir).
-92 net lines
Remove entire TaskState machinery:
- TaskState struct, render(), taskState()/updateTaskState() on History
- inferTaskStateUpdate() — heuristic plan tracking
- Replan gate (replanGate const, roundsWithoutPlan counter)
- Stall detection (stallThreshold, stallRounds, lastAction)
- Plan reinject (planReinjectInterval, periodic re-injection)
- ReadPlanStep plumbing (AgentCfg → Agent → TurnCtx → loop)
None of this worked in practice — the model ignores the nudges,
ReadPlanStep was never wired to a non-nil value, and TaskState
fields were only populated by inferTaskStateUpdate (which depends
on ReadPlanStep being set).
Compaction now uses plain summary text in the cold zone instead
of serialized TaskState JSON.
Also remove:
- Verbosity field (declared, threaded, never read by any backend)
- DisallowTools (config field never wired to session)
- Session.listHandlers, plan, prevPrompt (never read)
- Config.Output, Config.ListHandlers, Config.ReadPlanStep (never set)
-246 net lines
- Export ID, Uname, Ctx, Cancel, Proc, Keeper, Remote as struct fields
(set-once at creation, not raced during agent loop)
- Keep mutex-protected accessors only for Name, ToolsConn, IsPaused
which are read/written concurrently
- Extract buildAgent() shared helper used by both CreateAgent and
restoreMultiAgentSession — eliminates ~100 lines of duplicated
orchestration (tool server setup, prompt layers, runtime, backend)
- Paused sessions skip tool server entirely during restore (minimal
agent with just history)
- Update fs/session_node.go delegators for exported fields
- 1638 → 1523 lines (-115)
Replace the monolithic preamble string with Preamble struct — an
ordered list of named sections joined on demand. Updates are simple
Set() calls; no marker-based string splicing.
Changes:
- Delete prompts/tool_discovery.md (content already in system_prompt.md)
- Reduce prompts/ package to just the embed + ResolveSystemPrompt
- Replace buildPreamble() with direct section construction
- Replace refreshToolListing() 90-line string surgery with one Set()
- Replace SetCWD string.ReplaceAll with env section rebuild
- Remove RenamePreamble (was a no-op; session ID is a template var)
- Export EnvironmentBlock() for session setup
- All callers use PreambleString() instead of raw string field
toolsrv:
- Merge rpc.go into conn.go (wire types already there)
- Merge tools.go + discover.go into registry.go
- Merge accessors.go into server.go
- 4 files eliminated
fs:
- HandlerCtx.Context now carries the session context (set via
sessionBindings Applier). Previously it was the daemon context
which never cancels — handlers checking ctx.Done() now properly
detect session kill/pause.
- Session-scoped blocking handlers (streamAgentChat, blockAgentStateWait)
merge session context with per-read timeout via mergeCtx(). A session
kill now immediately unblocks readers without waiting for the 5s timeout.
- Remove redundant manual AfterFunc in blockAgentStateWait.
session:
- Remove unused _ string parameter from WaitEvent
The audit logger was never a separate destination — it was always
log.Sub("audit"), writing to the same sink with a prefix. The
fallback in NewAgent even warned 'deriving from Log' every time.
Replace ag.auditLog.Debug() calls with ag.log.Debug() directly.
Remove the field, AgentCfg.AuditLog, and all session plumbing.
The agent always talks to a *Conn (never a *Server directly).
Replace the Runner interface with the concrete type throughout:
- Runtime.ToolServer is now *toolsrv.Conn
- BuildRuntime takes *toolsrv.Conn
- session.Config, SessionInfra, InfraConfig use *toolsrv.Conn
- All inline type assertions in agent.go deleted — methods called
directly on *Conn (SetEnv, SetCWD, Close, Detach, ListDetachedRaw,
SignalDetached, GetDetachedOutput, DismissDetached, SetAllowTools,
SetOnToolsChanged, Ping, ToolRegistryRevision)
- Added Conn.ToolRegistryRevision() that returns 0 (remote connections
don't support revision tracking; use push-based OnToolsChanged instead)
- Deleted the Runner interface and its test
- Renamed LoadToolOnRunner → LoadToolOnConn
- Kill agentConfig struct. run() takes (*Runtime, TurnCtx, state) directly.
TurnCtx holds only 7 per-turn closures; stable config reads come from Runtime.
- Lazy tool refresh: toolsNeedRefresh() checks ToolRegistryRevision() on the
tool server. Skips ListTools IPC when registry hasn't changed. Remote Conn
falls back to always-refetch (same as before).
- AgentConfig.GenParams() method replaces the 16-line manual field copy in
BuildRuntime.
- Remove CfgBackend/CfgModel from Runtime. Callers read cfg.Backend/cfg.Model
directly since they already have the AgentConfig in scope.
- Move FormatEvent to format/ package for reuse by both live handler
and replay
- Move LoadConfig to agent/ package where it belongs
- Remove log validator (was noisy, not essential)
- Update replay to use FormatEvent for consistent output format
- Update kde submodule with GUI streaming fix
- Stripped OLLIE_TOOLS_PATH, OLLIE_AGENTS_PATH, OLLIE_SKILLS_PATH,
OLLIE_PROMPTS_PATH, OLLIE_MEMORY_PATH, OLLIE_TMP_PATH from managed
list and defaults in env.go
- ToolsPath(), SkillsPath(), PromptsDirs(), AgentsDirs(), ollieTmpDir()
now derive from paths.CfgDir()/paths.DataDir() directly
- Removed shell-exec fallback from resolveExecPrompt() — only file
resolution remains
- Cleaned up env_test.go to match reduced managed set
- Updated stale comments referencing /home/lkn/.config/ollie/tools
Kill is unconditional — a killed session must not be restored
on daemon restart. Call RemovePersistedSession() to delete the
session file from ~/.config/ollie/sessions/active/.
- Skip SetupToolServer for paused sessions during restore
- Pause() and Resume() now persist state to disk
- Resume() sets up tool server from scratch for sessions restored paused
- IsConnected() returns false when paused
- Add SetPaused(), SetToolServer() methods
- ProcessKeeper.SetContext() for resume after pause
- Session index sorts active-first, paused-last
- CreateAgent accepts name= parameter
- Remove duplicate SetCwd method
- CreateEmpty(name, remote) now spawns remote toolsrv when remote is set
- requestSessionNew parses remote= argument
- Add Session.SetRemote() method
- Update kde submodule
- Rename AgentCfg.SaveSession/FlushSave to Save/Flush for brevity
- Remove restoreLegacySession — all sessions now use multi-agent format
- Pass Save/Flush callbacks to agents in CreateAgent and restore paths
- Add AuditLog fallback warning in NewAgent
After the fs/session refactor, NewEventHandler was defined but never
called. Agents created via session restore or CreateAgent had nil
output handlers, so emit() was a no-op and the chat stream stayed empty.
- Add Agent.SetOutput() to allow setting the event handler post-creation
- Wire up SetOutput(NewEventHandler(al)) in fs/newroot.go for restored sessions
- Wire up SetOutput(NewEventHandler(al)) in fs/handlers.go for new agents
- Also includes expanded UsageStats fields and TaskState persistence
- flushSave and SaveSession now use PersistSession instead of
agent.SaveFull, ensuring the multi-agent format with Name field
is consistently written
- Rename now calls PersistSession immediately so renamed sessions
survive server restart
The previous approach had two save formats (legacy single-agent via
agent.SaveFull, and new multi-agent via PersistSession) writing to
the same file. Since flushSave ran on every turn, it would overwrite
the Name field with the legacy format that didn't preserve it.
flushSave and SaveSession were passing empty string instead of
a.Name() as the sessionName argument to agent.SaveFull, causing
the session name to be lost on restart.
session/persist.go:
- New multi-agent format: PersistedSession with []PersistedAgent
- PersistSession saves ALL agents, not just first
- RestoreAllSessions returns []RestoredSession with []RestoredAgent
- Backward compatible: restores legacy single-agent format
fs/persist.go:
- Thin wrapper that creates AgentLogs for each restored agent
- Delegates to session.PersistSession/RestoreAllSessions
agent/agent.go:
- Added Messages() to get conversation history
- Added Usage() to get token/cost stats
agent/history.go:
- Added RestoreHistoryFromMessages for multi-agent restore
session/session.go:
- Added Remote() getter
Add 'kill' command to agent ctl (removes agent from session).
Add Session.RemoveAgent() method.
Remove activeAgentID field, Agent(), SetActiveAgent, and dead
WaitChange — 'active agent' is a GUI concept, not a server one.
All callers updated to iterate Agents() or use FindAgent(id).
- Session.Agent() now looks up activeAgentName in the agents slice
instead of returning agents[0]. Falls back to first agent if unset.
- Added SetActiveAgent(), FindAgent(), SaveFuncs() methods.
- New() and AddAgent() set activeAgentName automatically.
- CreateAgent: refactored to ADD to existing core session instead
of overwriting it. If sess.Core exists, builds the new agent via
agent.NewAgent() and adds it via Core.AddAgent().
- agent/new now lists all agents in the session directory.
- 9P path handlers (rootReaddir/Stat/Open) use FindAgent() and
AgentLogFor() to resolve agents by name/ID instead of using
the first agent exclusively.
- Added AgentLogFor(id) to fs Session for per-agent log lookup.
persistSession: check sess.Core.Agent() != nil before saving.
silently skip sessions with no agent instead of logging
'no active session' error on shutdown.
SaveSession: return nil (no-op) when there are no agents,
rather than returning an error that gets logged.