7.1 KiB
Observations
Filesystem as interface — if you know how to operate a filesystem, you know how to operate ollie. Any tool that reads and writes files — ed, vim, Python, cat, a shell script — is already a fully capable ollie client. No SDK, no API, no protocol to learn.
Simple front-ends — A front-end reads chat, writes prompt, polls state. There is no protocol to implement.
Crash resiliency — Memory (m/) is written to persistent storage outside the session. If a session crashes or is killed mid-task, it is not lost — a fresh session picks up where the previous one left off.
Dynamic tool loading — After transitioning from execute_code/call_tool/pipe to a unified shell primitive, ollie adopted a dynamic lazy-loading architecture. The system has 7 primitives: shell (sandboxed), tool_load/tool_list/tool_active, skill_load/skill_list/skill_active, plus associated 9P filesystem endpoints. Tools are executable scripts discovered from OLLIE_TOOLS_PATH; skills are SKILL.md directories discovered from OLLIE_SKILLS_PATH. Both use session-scoped registries with per-session load tracking and dynamic discovery — tools and skills can be added, modified, or removed at any time without restarting the server. The agent autonomously loads tools and skills as needed — no explicit user permission required.
Both shell and tools are sandboxed — Promoted tools are not exempt from sandboxing. They execute through the same executeWithStdin path as inline shell commands, running inside a Landlock sandbox with the same per-profile filesystem access rules. The sandbox applies to every code path that touches user data — there is no bypass by loading a tool.
Error rates significantly improved after the facade layer — The original architecture exposed execute_code/call_tool/pipe as the only primitives. Larger models with strong reasoning handled this well. Smaller and cheaper models consistently failed: wrong tool names, incorrect argument formats, confused execution modes, ignoring named tool scripts in favour of reimplementing them inline. The facade layer — tool_load, tool_list, tool_active — promotes a tool script to a first-class callable function with a JSON schema. Error rates dropped dramatically because the model no longer needs to construct the correct invocation string; it calls a native function with typed arguments. The tool scripts themselves are unchanged — they are still executable files on disk. The facade is purely a contract layer between the agent and the tool ecosystem. This preserves the simplicity of a small core while supporting a rich ecosystem of specialized capabilities, and it costs nothing for models that already reasoned well through the abstraction.
Parallel execution — execute_code accepts a steps array of pipeline stages. A {parallel: [...]} stage fans out concurrently within the pipeline. Sequential stages chain stdout → stdin. All stages accept inline code and named tool scripts interchangeably.
Radical transparency — The rendered system prompt for any session is readable at s/{id}/systemprompt — the prompt the agent is actually running with, not a reconstruction. The prompt parts that compose it live in p/; the agent can read its own instructions but cannot modify them.
Discoverability — ls s/{id}/ shows every control surface. Tools live at $OLLIE_TOOLS_PATH, skills at $OLLIE_SKILLS_PATH. cat help explains the rest.
Composability — execute_code stages chain the same way Unix pipes chain programs. Sessions chain by reading s/{id}/chat and writing to another session's prompt or fifo.in.
Passive memory — Every session's chat history accumulates at s/{id}/chat, searchable with grep. An agent doesn't need to actively decide what to remember. This extends across sessions: an agent can search another session's chat for answers that have already been worked through.
Sub-agents — subagent_spawn creates an independent session under s/, submits a prompt, and returns immediately with the session path. Subagents coordinate with the parent and each other by writing to each other's prompt files — direct async message passing over the filesystem. For parallel spawn, a {parallel: [...]} stage in execute_code starts multiple subagents simultaneously.
No MCP — MCP (Model Context Protocol) adds a JSON-RPC client, transport layer, initialization handshake, and subprocess lifecycle management to do what execute_code {tool: "name"} already does. Every MCP server can be replaced by a tool script in t/ and a skill in sk/. x/elevate proved this: it replaced an MCP server with a shell script. See doc/NO_MCP.md for the full justification.
Introspection and State Visibility — A significant architectural strength of Ollie is the total transparency of agent state via the 9P filesystem. When tasked with "introspecting" on a complex, multi-agent session, an agent demonstrated the ability to (1) use standard file tools (tail, grep) to read the chat and prompt files of subagents to recover semantic state, (2) synthesize current session memory with data extracted from peer sessions, and (3) directly verify that requested work was performed by inspecting worktrees and git history. This ensures that hallucinations and stuck states are observable system states that can be diagnosed and corrected through standard administrative primitives.
AI pipelines with standard tools — ollie scripts compose into multi-stage AI pipelines via Unix pipes. Proof-of-concept:
; u/optimize -n 2 -cm qwen/qwen3-8b -jm 'anthropic/claude-opus-4.6' 'generate a hello world program. use c++.' >[2]/dev/null | s/bfg
u/optimize generates and judges candidate prompts, emitting the best to stdout; s/bfg acts on it. Any tool that reads stdin and writes stdout is already a pipeline stage.
Prompt composition — s/bfg outputs can be nested to arbitrary depth using shell command substitution. The inner prompt completes, its output becomes part of the outer prompt:
; echo 'translate to japanese: '`echo 'say hello' | s/bfg` | s/bfg
こんにちは!👋
This enables prompt pipelines where each stage transforms, filters, or augments the previous stage's output — all with standard shell syntax.
Tool schema complexity and model capability — The execute package originally exposed a single execute_code tool with a unified schema covering inline steps, stdin-chained pipelines, named tool scripts, parallel fan-out, elevated execution, and sandbox selection. Larger models with strong reasoning handled this without difficulty. Smaller models struggled: common failure modes included confusing execution modes and ignoring named tool scripts in favour of reimplementing them inline. The tool was therefore split into two primitives — execute_code (inline code) and the tool registry (named scripts promoted to native callables) — giving smaller models a narrow, unambiguous contract per tool. The {parallel: [...]} fan-out option is retained inside execute_code stages.