From a94454d72bc8d9ed960122e60f8d892149ced8e5 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Thu, 21 May 2026 15:08:46 +0200 Subject: [PATCH] update core submodule (session persistence), sync docs --- core | 2 +- doc/ARCHITECTURE.md | 5 +-- doc/CORE.md | 87 +++++++++++++++++++++++++++++++++------------ 3 files changed, 69 insertions(+), 25 deletions(-) diff --git a/core b/core index 9d4818a..8ae0fad 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 9d4818af8da4ce238181aa83bd9ed0332260f109 +Subproject commit 8ae0fad8a302b93d3e13f5b6b6656508f7137b1d diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md index e294093..8949ab1 100644 --- a/doc/ARCHITECTURE.md +++ b/doc/ARCHITECTURE.md @@ -89,7 +89,7 @@ The core is a Go library (`ollie/pkg/`) with no binary. Frontends import it as a | Package | Purpose | |---|---| -| `pkg/agent/` | Agent loop, Core interface, Session, TaskState, hooks, prompt resolution | +| `pkg/agent/` | Agent loop, Core interface, Runtime, Session, TaskState, hooks, prompt resolution | | `pkg/backend/` | Backend interface + implementations (Ollama, OpenAI, Anthropic) | | `pkg/config/` | Agent config struct and JSON loader | | `pkg/env/` | Environment variable loading (env file + shell) | @@ -97,6 +97,7 @@ The core is a Go library (`ollie/pkg/`) with no binary. Frontends import it as a | `pkg/paths/` | Config and data directory resolution | | `pkg/tools/` | Server and Dispatcher interfaces | | `pkg/tools/execute/` | The execute.Server (execute_code, call_tool, pipe) | +| `pkg/tools/execute/tool.go` | Named tool script discovery and execution | | `internal/sandbox/` | Landlock sandbox config and command wrapper | ### Key Interfaces @@ -165,7 +166,7 @@ Safety mechanisms: `Session` owns the message history as a flat `[]backend.Message` slice. Key behaviors: - **TaskState**: structured JSON overlay (objective, plan_step, constraints, last_action, next_decision) injected at the top of every turn -- **Compaction**: three-zone strategy when context reaches 75% of model limit: +- **Compaction**: three-zone strategy when context reaches 50% of model limit: - **Cold zone**: structured task state summary - **Warm zone**: one-line decision index (10 messages) - **Hot zone**: last 8 messages verbatim diff --git a/doc/CORE.md b/doc/CORE.md index e27dc40..0772e45 100644 --- a/doc/CORE.md +++ b/doc/CORE.md @@ -9,7 +9,8 @@ core/ ├── pkg/ │ ├── agent/ The agent engine │ │ ├── types.go Core interface, Event, PromptFIFO -│ │ ├── core.go AgentCoreConfig, agent struct, NewAgentCore, BuildAgentEnv +│ │ ├── runtime.go Runtime struct (swappable per-agent config) +│ │ ├── core.go AgentCoreConfig, agent struct, NewAgentCore, BuildRuntime │ │ ├── loop.go The agent loop (run function) │ │ ├── session.go Session, TaskState, compaction, persistence │ │ ├── state.go state interface, ResultTier, toolResult @@ -31,6 +32,7 @@ core/ │ └── execute/ The execute.Server │ ├── server.go Server struct, Decl factory, ListTools, options │ ├── code.go Step resolution, validation, dispatch routing +│ ├── tool.go Named tool script discovery and execution │ ├── pipe.go Pipeline stage execution, parallel fan-out │ ├── flock.go Advisory locking, parallelism classification │ └── tier.go Result tier classification (hot/warm/cold) @@ -104,37 +106,65 @@ The full construction config: | `Uname` | Immutable 9P user principal | | `CWD` | Working directory for tools and prompt | | `Session` | Pre-existing session to restore (nil = new) | -| `Env` | Pre-built AgentEnv (tools, hooks, prompt, params) | +| `Runtime` | Pre-built *Runtime (tools, hooks, prompt, params) | | `NewDispatcher` | Factory for fresh Dispatcher on agent switch | | `NewBackend` | Factory for backend by name | | `Log` | Logger instance | | `MaxSteps` | Override agent config's maxSteps | -### BuildAgentEnv +### Runtime -`BuildAgentEnv(cfg, dispatcher, cwd, env)` wires everything together: +The `Runtime` struct holds all per-agent configuration that changes on an `/agent` switch but remains stable across turns. The agent struct stores a pointer to the active Runtime; switching agents replaces it atomically. + +```go +type Runtime struct { + Backend backend.Backend + Dispatcher tools.Dispatcher + Hooks Hooks + Preamble string // compiled system prompt + Tools []backend.Tool + Exec toolExecutor + ClassifyTool toolClassifier + ClassifyTier func(string) ResultTier + GenParams backend.GenerationParams + MaxSteps int + ToolResultMaxBytes int // 0 means use default (128KB) + CfgBackend string // backend override from agent JSON + CfgModel string // model override from agent JSON + Messages []string // non-fatal warnings from construction +} +``` + +### BuildRuntime + +`BuildRuntime(cfg, dispatcher, cwd, env)` wires everything together: 1. Lists all tools from the dispatcher 2. Resolves the system prompt by executing the config's `prompt` commands 3. Extracts generation parameters from the config 4. Builds the tool executor closure (routes calls through the dispatcher) 5. Extracts parallelism and tier classifiers from the execute server -6. Returns an `AgentEnv` struct ready for `NewAgentCore` +6. Applies tool/executor restrictions if configured +7. Returns a `*Runtime` struct ready for `NewAgentCore` ### NewAgentCore -Creates the `agent` struct: +Creates the `agent` struct from `AgentCoreConfig`: 1. Sweeps stale tmpdirs from previous crashes -2. Creates `/tmp/ollie/{sessionID}` for this session -3. Sets up the plan file reader (reads `s/{id}/plan` for the next unchecked step) -4. Wires the `turnError` hook with a detached context -5. Pushes `OLLIE_SESSION_ID` and `OLLIE_UNAME` into the execute server env -6. Sets the flock directory for parallel coordination +2. Stores the provided `Runtime` (or creates an empty one) +3. Sets the backend model if `ModelName` is specified +4. Creates `/tmp/ollie/{sessionID}` for this session +5. Sets up the plan file reader (reads `s/{id}/plan` for the next unchecked step) +6. Wires the `turnError` hook with a detached context +7. Pushes `OLLIE_SESSION_ID` and `OLLIE_UNAME` into the execute server env +8. Sets the flock directory for parallel coordination ## The Agent Loop (`pkg/agent/loop.go`) The `run()` function is the core execution engine. It takes an `agentConfig` and a `state` interface, and loops until the model stops calling tools. +The `agentConfig` struct bundles per-turn dependencies: `Backend`, `Tools`, `Exec` (tool executor closure), `ClassifyTool`/`ClassifyTier` (parallelism and retention classifiers), `ToolResultMaxBytes`, `Output` (event handler), `preamble`, `GenerationParams`, and lifecycle callbacks: `PopInject`, `AutoCompact`, `SaveSession`, `PreTool`, `PostTool`, `TurnError`, `MaxSteps`, `ReadPlanStep`, and `IncrToolCallCount`. + ### Per-Iteration Flow ``` @@ -208,6 +238,8 @@ After each tool round, `inferTaskStateUpdate` updates the structured overlay: `Session` holds a flat `[]backend.Message` slice. No rolling window — grows until compaction. +**Checkpoint**: `Session.Checkpoint(ts TaskState)` forks the session — returns a new Session that inherits the given TaskState but starts with a clean message history. Used for narrow-context sub-agents that know what to do without inheriting parent message noise. + Token tracking per session: - `TotalInputTokens`, `TotalOutputTokens`, `TotalRequests` - `TotalCachedInputTokens`, `TotalCacheCreationTokens` @@ -216,7 +248,7 @@ Token tracking per session: ### Compaction -Triggered when estimated tokens reach 75% of model context length. +Triggered when estimated tokens reach 50% of model context length. **Three-zone strategy:** @@ -289,27 +321,38 @@ Multiple commands per hook run sequentially. If any exits 2, execution stops imm | Prefix | Dispatch | |---|---| | `!` | Shell command in session CWD | -| `/backend ` | Switch LLM backend | -| `/model ` | Switch model | -| `/agent ` | Switch agent config (rebuilds dispatcher) | -| `/compact` | Manual compaction | -| `/context` | Show message breakdown | -| `/usage` | Show token counts | -| `/cost` | Show cost | +| `/backend [name]` | Show or switch LLM backend | +| `/model [name]` | Show or switch model | | `/models` | List available models | +| `/agent [name]` | Show or switch agent config (rebuilds Runtime) | | `/agents` | List available agents | -| `/maxsteps [n]` | Get/set max steps | +| `/maxsteps [n]` | Get/set max steps (0 = unlimited) | +| `/compact` | Manual compaction | +| `/context` | Show context size and message breakdown | +| `/usage` | Show token usage and context percentage | +| `/cost` | Show last turn and session cost | +| `/history` | Dump bounded message history | +| `/sessions` | List saved sessions | +| `/clear` | Clear session (reset context) | +| `/kill` | Kill session | +| `/rn ` | Rename session | +| `/cwd [path]` | Show or change working directory | +| `/skills` | List available skills | +| `/tools` | List available tools | +| `/sp` | Show rendered system prompt | | `/i ` | Inject into running turn | | `/irw ` | Inject-rewrite | +| `/queued [pop\|clear]` | Manage queued prompts | +| `/help` | Show command help | ### Agent Switching When `/agent ` is issued: 1. Load the new agent config JSON 2. Create a fresh dispatcher via `newDispatcher()` -3. Call `BuildAgentEnv` with the new config +3. Call `BuildRuntime` with the new config 4. If the config specifies a backend/model, switch those too -5. Replace dispatcher, hooks, prompt, tools, params on the agent struct +5. Replace the entire `Runtime` pointer on the agent struct 6. Clear the session (fresh context for the new persona) ## Tool System (`pkg/tools/`)