9.2 KiB
Ollie Architecture
Ollie is a small Go runtime whose integration boundary is a 9P filesystem. The current architecture has one control-plane service, olliesrv, and a separate tool-execution service, toolsrv.
Boundaries
flowchart TB
C[9P clients and frontends] --> S[olliesrv]
S --> F[fs.Tree and session namespace]
S --> A[agent loop]
A --> B[provider backend]
A --> T[toolsrv]
T --> R[tool registry]
T --> X[landrun / Landlock sandbox]
T --> P[bypass broker]
olliesrv
The daemon constructs an fs.Tree. The tree is the session collection; there is no separate manager abstraction. Session operations are package functions over the tree. A session owns its agents, context, cancellation, and lifecycle. An agent owns history and runs the model/tool loop.
The daemon exposes the 9P namespace directly. Reads and writes are the protocol: writing session/new creates a session, writing an agent's prompt submits work, reading chat observes history, and reading statewait blocks until a state transition.
Agent loop
The loop resolves system and user prompts, renders context, calls the configured backend, parses streamed responses, executes requested tools, appends results to history, and repeats. context.Context cancellation flows from the daemon through the session and agent. Context management handles token statistics, output limits, retries, and compaction.
Toolsrv
toolsrv is a separate process connected through 9P. It owns tool discovery, metadata, lazy registry loading, process lifecycle, output streaming, and sandbox invocation. Tool implementations are installed executables or scripts; they are not compiled into the agent loop. The same service can expose local and remotely deployed tools.
The tool namespace also provides process and sandbox state. Normal execution is restricted by configured Landlock/landrun policy. The bypass broker is a policy-controlled path for explicitly approved operations outside that policy.
Namespace
session/
├── new
├── idx
└── {session}/
├── plan
├── env
└── agent/{agent}/
├── cfg ctl state statewait
├── prompt prompt.prev chat offset
├── fifo.in fifo.out
├── context systemprompt usage cost ctxsz models
├── tools toolsrv.loaded toolsrv.rev
├── tail
└── proc/
The namespace is intentionally composable: Unix programs, editor integrations, shell scripts, and GUI clients can use the same operations without a client-specific RPC API. Authentication and permissions are provided by the 9P transport and the service's filesystem policy.
Configuration and data
Installed runtime data includes backend configuration, agent definitions, prompt templates, skills, and tool executables. The runtime resolves paths under the XDG configuration/data locations. OptMem owns persistent memory storage; the memory tools call it directly instead of maintaining a parallel Ollie store.
Multi-agent operation
subagent_spawn creates child agents with independent runtime state. Cascade-style fan-out and observer/feed agents are orchestration patterns built on the same session and 9P primitives, not a second agent runtime. Parent and child sessions exchange prompts and results through files.
Design constraints
The core deliberately avoids framework-heavy abstractions. Provider selection, tool discovery, sandboxing, and user interfaces are replaceable boundaries; the stable contract is the 9P namespace and its blocking read/write behavior.
Boot sequence
olliesrv starts in two layers: main() resolves the 9P namespace and delegates to runServer, then the server builds the filesystem, starts listeners, restores sessions, and waits for termination.
Call chain
main()
│
├── flag.CommandLine.Parse(os.Args[1:])
├── client.Namespace() — resolve the 9P namespace
├── filepath.Join(ns, "ollie") — derive the Unix socket path
└── runServer(sockPath)
│
├── util.EnsureEnv() — create/populate XDG runtime paths
├── remove stale ollie and toolsrv sockets
├── olog.NewSink(...) — configure logging from $OLLIE_LOG
├── agent.AgentsDirs() — resolve agent configuration
├── context.WithCancel(...) — create daemon lifetime context
├── fs.NewModelCache() — create the asynchronous model cache
├── dbus.SessionBus() — optionally configure desktop bypass notifications
├── fs.NewRoot(fs.Config{...})
│ ├── session.Init(...) — install runtime dependencies
│ ├── buildTreeSpec(...) — create the namespace specification
│ └── virtfs.BuildTree — build the 9P filesystem tree
├── New(Config{RootTree: rootTree}) — create the 9P server
├── go modelCache.Get() — prefetch models
├── net.Listen("unix", sockPath)
├── accept loop — each connection runs srv.Start(conn)
├── fs.RestoreSessions() — restore persisted sessions after listening
├── optional net.Listen("tcp", tcpAddr)
└── wait for SIGINT/SIGTERM
Why fs.NewRoot is the composition root
The 9P filesystem is the runtime boundary. fs.NewRoot installs the session package configuration and builds the complete namespace from the filesystem specification. It does not restore sessions; restoration is deliberately deferred until the Unix listener is ready so restored agents can be wired to the live tree and event paths.
main.go is deliberately thin: parse flags, resolve the namespace, and call runServer. runServer owns startup, listener management, signal handling, and shutdown.
Key ordering constraints
util.EnsureEnv()runs before paths, logging, or persistent state are used.- The stale Unix socket is removed before
net.Listen("unix", sockPath). session.Init()runs insidefs.NewRootbefore any session restoration.- The model cache is created before the root and prefetched asynchronously after the server is built.
fs.RestoreSessions()runs after the Unix listener and accept loop are ready.- The optional TCP listener starts after session restoration and uses the same 9P server.
- Shutdown closes listeners, cancels the daemon context, kills the server, removes the Unix socket, and flushes logs.
Session restoration
fs.RestoreSessions() reads JSON files from the active-session directory through session.RestoreAllSessions(). Restoration runs concurrently per file. Invalid files are logged and removed. Paused sessions rebuild minimal agents without a tool server or backend connection; active sessions rebuild their agents and connections. Restored agents are replayed into the live filesystem and wired to agent events after the listener is ready.
Context cancellation
Cancellation propagates from an agent interrupt through the active turn, the 9P tool call, and the tool process:
sequenceDiagram
participant U as Client
participant A as Agent
participant C as toolsrv client
participant S as toolsrv 9P server
participant P as Tool process
U->>A: write stop to agent ctl
A->>A: cancel active action with cause
A->>C: execute tool with turn context
C->>C: close proc fid when ctx.Done()
C->>S: Tclunk
S->>S: cancel request context
S->>P: CommandContext terminates process
P-->>A: cancellation error
A-->>U: cancelled turn state/result
The daemon owns a root context. Each session derives its own context; each agent turn derives a cancellable action context with context.WithCancelCause. The turn context is passed to backend calls and tool execution. An interrupt cancels the active action without cancelling the entire session.
The toolsrv client watches the call context while reading the blocking 9P result. Cancellation closes the request fid, which sends Tclunk to toolsrv. The request entry's close function cancels the server-side request context. Tool execution uses exec.CommandContext, so the child process is terminated when that context is cancelled. The agent loop checks cancellation before tool execution and returns a cancelled result instead of starting another tool.
Daemon shutdown cancels the root context, which propagates through sessions, agents, tool clients, and server-side requests. Closing listeners and killing the 9P server completes transport shutdown; the daemon then removes its Unix socket and flushes logs.
Relevant implementation points:
cmd/olliesrv/internal/agent/agent.go— interrupt cancels the active action.cmd/olliesrv/internal/agent/turn.go— creates the action context for each turn.cmd/olliesrv/internal/agent/loop.go— checks cancellation before executing tools.cmd/olliesrv/internal/toolclient/spawn.go— runs local and remote toolsrv processes with derived contexts.cmd/toolsrv/internal/server/proc.go— binds request cancellation to tool execution.cmd/toolsrv/internal/exec/exec.go— executes tools withexec.CommandContext.