From 97b14dfce3f9dd5efe71c1bcfffef5582bbc49c7 Mon Sep 17 00:00:00 2001 From: Ollie Agent Date: Sun, 16 Aug 2026 19:23:27 +0200 Subject: [PATCH] Update and consolidate boot sequence documentation --- doc/architecture.md | 54 ++++++++++++++++++++++++++++++++++++++++++++ doc/boot-sequence.md | 54 -------------------------------------------- 2 files changed, 54 insertions(+), 54 deletions(-) delete mode 100644 doc/boot-sequence.md diff --git a/doc/architecture.md b/doc/architecture.md index bc2165a..5f4a70f 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -64,3 +64,57 @@ Installed runtime data includes backend configuration, agent definitions, prompt ## 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 + +```text +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 + +1. `util.EnsureEnv()` runs before paths, logging, or persistent state are used. +2. The stale Unix socket is removed before `net.Listen("unix", sockPath)`. +3. `session.Init()` runs inside `fs.NewRoot` before any session restoration. +4. The model cache is created before the root and prefetched asynchronously after the server is built. +5. `fs.RestoreSessions()` runs after the Unix listener and accept loop are ready. +6. The optional TCP listener starts after session restoration and uses the same 9P server. +7. 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. diff --git a/doc/boot-sequence.md b/doc/boot-sequence.md deleted file mode 100644 index b8329db..0000000 --- a/doc/boot-sequence.md +++ /dev/null @@ -1,54 +0,0 @@ -# Boot Sequence - -How `olliesrv` starts up, from `main()` to serving 9P requests. - -## Call chain - -``` -main() - │ - ├── env.EnsureDefaults() — populate $XDG_CONFIG_HOME/ollie/ if missing - ├── olog.NewSink() — stderr/stdout log sink - ├── fs.NewModelCache() — async LLM model list (prefetched later) - │ - ├── fs.NewRoot(cfg) ← composition root - │ │ - │ ├── session.Init(...) — set package-level config (ctx, dirs, logger) - │ ├── build RootState — allocate SessionNodes map - │ ├── fsedsl.BuildTree(spec) — walk fs/spec.go, produce *Tree with lazy resolution - │ └── session.RestoreAllSessions() - │ └── for each .json in sessions/active/ - │ ├── unmarshal PersistedSession - │ ├── rebuild Session + Agents (paused) - │ └── register in session registry - │ - ├── bypass.NewBroker(...) — start bypass socket (sandbox escape approval) - ├── New(serverCfg) — create 9P server wrapping the tree - ├── net.Listen("unix", sockPath) — bind the 9P socket - └── accept loop — each conn gets srv.Start(conn) -``` - -## Why `fs.NewRoot` is the composition root - -The 9P filesystem IS the runtime. Every session, agent, and tool server is -reachable only through the namespace. So the filesystem package owns -initialization: it creates the tree, initializes the session registry, and -restores persisted state — all before a single 9P connection is accepted. - -`main.go` is deliberately thin: parse flags, set up logging, call `fs.NewRoot`, -start the listener. All domain logic lives in the packages. - -## Key ordering constraints - -1. `session.Init()` must be called before `RestoreAllSessions()` (sets pkg config). -2. `RootState` must be allocated before `BuildTree()` (closures capture the pointer). -3. `BuildTree()` must complete before `RestoreAllSessions()` (restored sessions - create nodes in the tree). -4. The bypass broker starts AFTER the tree is built but BEFORE the accept loop - (tool execution needs the broker socket). - -## Session restoration - -Restored sessions come back **paused**. They have agents with history but no -running tool server or LLM connection. The first `Resume` (via ctl write) -re-dials the tool server and reconnects the backend.