- Delete agent/complete.go (110 lines)
- The kate plugin builds the completion prompt inline and calls
ollie-9p rdwr generate instead of ollie-9p rdwr complete
- Remove defaultCompactionModels map from compaction.go
- Add agent/models.go: load ~/.config/ollie/models.yaml for all backend/model settings
- Completion (Complete()) falls back to models.yaml completion section when env vars unset
- Compaction falls back to models.yaml compaction section per-backend
- Priority chain: agent config > OLLIE_*_MODEL env > models.yaml > session model
- Remove all OLLIE_*_PATH vars (TOOLS_PATH, CFG_PATH, DATA_PATH, etc.)
Use XDG_CONFIG_HOME/ollie/* and XDG_DATA_HOME/ollie/* instead
- Replace OLLIE_<TAG>_LOG per-component logging with single OLLIE_LOG={level}
- Remove Route()/RouteRequest/RouteResult (replaced by direct backend selection)
- Update sandbox config to use XDG paths instead of OLLIE_*_PATH tokens
- Update docs accordingly
- executeElevated: use e.sessionID instead of os.Getenv (process-global
env is empty in multi-session olliesrv)
- executeElevated: merge envExtra into broker env map so session-scoped
vars are visible to elevated commands
- Replace per-turn rate limit with x/time/rate leaky bucket:
burst of 3, refills 1 token per 10s. Only denials/timeouts consume
tokens; approvals are free.
If AGENTS.md exists in the working directory, its contents are injected
as a user message at session start and after compaction (via spawnContext).
Not part of the system prompt — lives in conversation history.
tool_load always re-reads the tool from disk (reload semantics).
tool_list calls RefreshLoaded to update schemas for all promoted tools.
Skills registry gets the same treatment: dynamic discovery, no cache.
Zero-context-cost scratchpad tool. The thought is recorded in history
but returns empty — no output tokens wasted. Registered in ListTools
schema and builtins map.
Registry.Summaries() and Load() now scan the tools directory on every
call instead of reading from a startup cache. New tools dropped into
the directory are immediately visible without restarting the server.
When tool_list is called, the OnToolsChanged hook fires and updates
the preamble's '# Available Tools' section in the live agent runtime.
No agent reload or session restart required.
Addr{Network, Host, CWD} describes how to reach a tool server.
Dial dispatches to local (in-process) or ssh (remote) based on Network.
TODO comment describes future path: local via unix socket.
tools.Server is now a concrete struct (the local execution engine).
tools.Runner is the minimal 2-method interface for polymorphism
(satisfied by both Server and RemoteServer).
Deleted: Dispatcher, CWDSetter, EnvSetter, ToolRestrictionSetter
interfaces. Agent uses inline type assertions where needed.
The execute/ package no longer exists.
Runtime.Dispatcher → Runtime.ExecServer (tools.Server). The Dispatcher
abstraction routed to exactly one server ('execute') — unnecessary
indirection. BuildRuntime now takes tools.Server directly.
Agent.newDispatcher → Agent.newToolServer. AgentCfg/Config updated.
All GetServer('execute') calls replaced with direct ExecServer access.
exec closure simplified: direct CallTool, no tool-name lookup.
Runtime is guaranteed non-nil by the caller (session.New ensures it).
Remove defensive checks that can never fire. execServer() retains
the Dispatcher nil check (agents without tools are valid).
No external callers — only used internally by the detach methods
(Detach, ListDetached, SignalDetached, GetDetachedOutput, DismissDetached).
The accessor is now private, no longer leaking the dispatcher interface.
Session.Submit now forwards directly to Agent.Submit. Slash commands
are agent-level concerns handled by Agent.HandleCommand.
Session-level operations (save, resume, kill, rename, cwd) are ctl
verbs issued via the ctl file — they are NOT slash commands and do
not flow through the prompt path.
This eliminates the artificial session/agent command routing and
makes the prompt path clean: everything in the prompt goes to the agent.
Agent is now in ollie/agent with proper encapsulation:
- Unexported fields, exported methods as the API
- Own constructor (agent.NewAgent)
- Owns: turn execution, history, runtime, hooks, commands, compaction
- Session never reaches into agent internals
Session (ollie/session) is a thin host:
- Owns: persistence, session ID, env, detach delegation
- Delegates all agent operations through exported Agent methods
- handleCommand dispatches to agent.HandleCommand for agent-level commands
Agent-level commands (/model, /backend, /compact, /agent, etc.) live
in agent/commands.go and access internals directly (same package).
Session-level commands (/sessions, /save, /resume, /cwd, /help)
remain in session/commands.go.
Test files temporarily removed pending rewrite against new API.
The fifo_test.go passes as a sanity check.
Agent now owns the full turn lifecycle: Submit, executeTurn, turnError,
autoCompactLimit, autoWarnLimit, spawnContext, and runCompact all live
on *Agent in turn.go.
Session.Submit is a thin dispatcher: slash commands are handled at
session level (they need sessionsDir, listHandlers, etc.), then
non-command input is delegated to Agent.Submit.
Session.IsRunning and Session.Interrupt delegate to Agent methods.
turnError is an overridable func field on Agent (default: hook-based
implementation in defaultTurnError). saveSession and flushSave are
injected as callbacks from Session into Agent at construction time.
This completes the agent/session ownership separation for the turn
execution path. The Agent is now a self-contained entity that can
run turns independently given its injected dependencies.
Agent now holds all dependencies needed to run turns independently:
bus, log, auditLog, sessionID, startupMessages, readPlanStep, saveSession.
emit() moved to Agent. Session no longer owns startupMessages/readPlanStep.
- uname is the agent's principal/UID, not a session property
- peers removed entirely from core: the concept needs redesign for
multi-agent. The 9P layer retains its own inter-session routing
at the Manager level.
Core Session now holds only: id, bus, env, plan, prevPrompt.
Agent holds all execution + identity state.
Agent now owns all execution-related state:
- cwd: the directory the agent operates in
- fifo: prompt queue (prompts target a specific agent)
- submitMu: serializes turns for this agent
- toolCallCount: per-agent tool usage metric
- pendingInject: injection targeting the current agent
Session retains only environment state: identity, bus, env, plan, peers,
persistence. This completes the agent/session state separation.
Agent now owns its execution state (state, reply, stateMu, changeMu,
changeCond). Session delegates State()/Reply()/WaitChange() to Agent.
This is the correct ownership: each agent has independent execution
state. In multi-agent, agents can be idle/thinking independently.
Session remains the coordination layer.
Programmable test backend that satisfies Backend interface without
network calls. ChatStreamFunc can be set to control behavior
(blocking, custom responses, errors).
- Session.Agent() returns the active *Agent
- Agent.Name(), Agent.BackendName(), Agent.ModelName() — first-class
- Session proxy methods delegate to Agent
Consumers can now access the agent directly:
sess.Agent().Name()
sess.Agent().BackendName()
The Session struct is defined in session.go — named after what it is.
No more 'harness' concept; that was an artifact of the interface+impl
pattern we removed.
Session is now an exported struct with unexported fields.
Methods ARE the API — no interface declaration in the package.
Consumers that need mockability define their own interface
(implicit satisfaction, standard Go pattern).
The agent/ package is now session/ — because a session is the
top-level concept. The package contains:
- harness: implements Core, the session orchestrator
- Agent: the reasoning entity (history, runtime, tools)
- History: conversation accumulator
- Fifo, loop, compaction, hooks, commands
The old session/ package (which just held extracted fields) is
dissolved back into the harness. One package, clean ownership.
The harness is the Core implementation that wires a session.Session
to an Agent and drives execution. A session has a harness; the
harness runs the agent.
The struct that implements Core is the session host — it owns a
session.Session and an Agent, orchestrating their interaction.
Renaming makes the architecture self-documenting:
sessionHost (implements Core)
├── sess *session.Session — runtime environment
└── agent *Agent — reasoning entity (swappable)
The inner struct that holds agent-specific state (history, runtime,
backend, tools, hooks) is now called Agent — because that's what it
is. A session owns an Agent; the Agent performs reasoning within
the session's environment.
Extend the core session with fields that are genuinely session
concerns currently duplicated in 9p/session:
- Plan (markdown checklist, survives compaction)
- PrevPrompt (last submitted prompt text)
- Peers (bidirectional session links)
The 9p/session layer can delegate to these in a future change.
Move agent-specific fields into a nested 'reasoning' struct:
- history, runtime, cfg (per-turn params)
- agentName, agentsDir
- baseLayers, promptEnvExtra
- newDispatcher, newBackend
- currentAction, warnedContext, resultCache
The agent struct is now the session host (owns sess + reasoning).
On /agent swap, a new reasoning can be built from the new config
while the session (identity, state, bus, env) remains stable.
The agent struct receiver was historically 's' (from when it was
called 'session'). Now that session is a separate concept, rename
to 'a' to match the type name.