ollie/doc/evolution.md

6.1 KiB

Architectural Evolution

How the Ollie system-of-systems emerged and evolved. This is a study log: it records important transitions, including discarded approaches, while describing the architecture that remains.

1. The filesystem became the control plane

Ollie began as a collection of components with a conventional programmatic API. The decisive change was to represent agent primitives as files over 9P. Sessions became directories; prompting became a write to prompt; state and conversation became readable files; statewait supplied blocking observation.

This made the control plane protocol-agnostic. A shell, editor, GUI, or another agent can operate Ollie with ordinary file operations.

2. Tool execution moved out of the core

The tool design went through external MCP servers, scripts, and a single built-in execution path. The current design keeps tools outside the agent loop in toolsrv, a separate 9P process. Executables describe themselves with metadata, and the registry discovers and loads them lazily.

This removed built-in-tool and prompt-bloat pressure. File, code-intelligence, memory, reasoning, and web capabilities use the same external-tool boundary. OptMem became the owner of persistent memory rather than Ollie maintaining a competing memory directory.

3. Planning became session state

Planning moved through several namespaces and tool-specific task APIs. The surviving form is the session-scoped plan file: an agent-controlled Markdown checklist that survives context compaction without imposing a separate planning service.

4. Multi-agent work used the same primitives

Subagents, generated agents, cascades, observers, and feed agents were added as orchestration patterns. They create ordinary sessions and agents, then communicate through 9P files. This avoided introducing a separate coordination protocol.

5. Security became process and policy isolation

Execution initially had no sandbox. Landrun/Landlock policy was introduced for tool processes, followed by per-process isolation and a policy-controlled bypass broker for approved operations. An SSH-agent proxy and separate privilege adapter were removed because they expanded the trust boundary without improving the core model.

6. Frontends stayed clients

A terminal UI and several adapter experiments were tried. The durable frontends are shell scripts, Acme, KDE integrations, and other 9P clients. They create sessions and observe files rather than duplicating the agent loop.

The earlier D-Bus control-plane experiment was also removed from the current architecture. Desktop integrations use the 9P service/client boundary instead of maintaining a second session store.

7. The great flattening

The core was simplified after the major features stabilized. Unused interfaces, manager/dispatcher indirections, duplicate execution packages, and dead exported code were removed. The fs.Tree became the session collection, package functions replaced the manager object, and context.Context replaced ad-hoc interruption paths.

The result is a single Go module with direct boundaries:

9P client → olliesrv → fs/session → agent loop → provider
                                  └→ toolsrv → sandbox/bypass

8. Toolsrv became an explicit service boundary

Tool execution was separated from olliesrv into toolsrv, a dedicated 9P service. The agent-side ToolsrvConn authenticates over 9P, scopes tool state per agent, and invokes local or background processes through the toolsrv namespace. The registry, metadata discovery, command resolution, sandbox, bypass approval, process lifecycle, and cancellation now belong to toolsrv rather than the agent core.

This boundary also supports remote execution. A remote session transfers the configured runtime data, starts toolsrv on the target host, and reaches it through an SSH-forwarded Unix socket. The agent loop, prompts, history, and model calls remain local.

9. The filesystem implementation was extracted into virtfs

The Ollie namespace and toolsrv namespace are now built with the shared virtfs Go library. FsNodeDecl constructors, closure-based handlers, dynamic Each bindings, aliases, and BuildTree keep namespace declaration separate from 9P transport. The filesystem remains the public interface, while virtfs supplies the reusable in-memory tree implementation.

10. Prompt assembly was simplified

Prompt construction was reduced to a named preamble assembled when an agent runtime is built. The runtime combines system, environment, agent, and tools sections, while userPrompts are prepended per turn. Tool metadata supplies both the model schema and the rendered tool documentation, removing duplicate tool-description paths and obsolete prompt-layer indirection.

11. Configuration moved into backend profiles

Backend selection, credentials, endpoints, models, and compaction models are now configured in backends.conf. The legacy backend and provider environment variables were removed. Environment variables remain for runtime concerns such as XDG paths, logging, process identity, and session context, but they are no longer the backend configuration interface.

12. Architecture became an integration map

The documentation was reorganized around explicit boundaries: 9P, the agent core, prompting, tool authoring, toolsrv, remote execution, and virtfs. Common framework features such as MCP clients, embedded tool frameworks, workflow engines, task graphs, frontend control planes, and competing memory stores are intentionally not built into Ollie. They integrate through tools, sessions, 9P files, and ordinary processes. OptMem and task systems such as Beads are examples of this composition model.

Current lesson

Ollie keeps one small runtime and makes capabilities composable through files and external executables. The architecture has changed repeatedly, but the durable principles are now clear: 9P is the integration contract, tools are replaceable processes, state is observable through files, and orchestration should be built from the same primitives as a single agent.