remove tools/builtin/ — zero built-in tools, all tools are external scripts

With the zero-tools transition complete, the tools/builtin/ package
served no purpose — Builtins() returned nil. Removed:

- tools/builtin/builtins.go and the directory
- builtins field, WithBuiltins, Dispatch from toolsrv.Server
- Handler type from toolsrv (only used by builtins)
- OnPreDispatch/WithOnPreDispatch (dead code)
- Strict field and WithStrict (dead code, never read)
- CallTool simplified — no fallback to Dispatch
- builtin import and WithBuiltins/WithStrict calls from ollie-remote
- tools/builtin from justfile build/test targets

Updated AGENTS.md, ARCHITECTURE.md, README.md, EVOLUTION.md
This commit is contained in:
Ollie Agent 2026-08-03 00:55:22 +02:00
parent 73eb4dea63
commit 60fafd5381
9 changed files with 199 additions and 113 deletions

View File

@ -15,7 +15,6 @@ ollie/ ← you are here
│ └── builder.go BuildTree — walks spec, validates, wires handlers
├── cmd/ (Go) Binaries (olliesrv, ollie-9p, ollie-remote)
├── tools/ (Go) Tool implementations:
│ ├── builtin/ Built-in handlers (shell, reasoning, tool/skill registry)
│ └── lsp/ LSP bridge + cmd/ binaries (definition, hover, rename, ...)
├── kde/ (C++/Qt6) KDE integration: plasmoid, GUI, Kate plugin, KRunner, tray
├── contrib/elisp (Elisp) Emacs frontend (ellie.el)

View File

@ -60,12 +60,10 @@ Single Go module with one Git submodule (kde).
|-----------|----------|-------------|
| `agent/` | Go | Agent loop, history, hooks, prompt resolution, commands |
| `backend/` | Go | LLM providers (Anthropic, OpenAI, Ollama, Gemini, Copilot, CodeWhisperer) |
| `toolsrv/` | Go | Tool server, registry, sandboxed execution, remote execution |
| `tools/builtin/` | Go | Built-in tool handlers (shell, reasoning, tool/skill registry) |
| `toolsrv/` | Go | Tool server, dynamic tool dispatch, sandboxed execution, remote execution |
| `tools/lsp/` | Go | LSP bridge daemon + client library (gopls, clangd, intelephense) |
| `session/` | Go | Session lifecycle, config, persistence |
| `fs/` | Go | 9P filesystem tree for session namespace |
| `dbus/` | Go | D-Bus adapter (`org.ollie.SessionManager`) |
| `detach/` | Go | Background process management |
| `elevate/` | Go | Elevation broker (privilege escalation) |
| `mount/` | Go | FUSE-based 9P mount for network transparency |
@ -87,17 +85,15 @@ graph TB
subgraph Frontends
ACME[Plan 9 acme]
EMACS[Emacs<br>ellie.el]
KDE[KDE<br>plasmoid / GUI / Kate<br>KRunner / tray]
KDE[KDE<br>GUI / Kate / KRunner]
end
subgraph "olliesrv"
P9[9P Filesystem<br>session/ namespace]
DBUS[D-Bus Adapter<br>org.ollie.SessionManager]
SM[Session Manager]
AG[agent.Agent<br>loop · compaction<br>prompt templates]
ROUTE[Router<br>complete · generate · route]
TOOLS[toolsrv.Server<br>dynamic registry · dispatch]
SB[Landlock Sandbox]
TOOLS[toolsrv.Server<br>dynamic dispatch · sandbox]
end
subgraph "LLM Backends"
@ -109,25 +105,20 @@ graph TB
end
subgraph Execution
LOCAL[Tool Scripts<br>file_* · lsp_* · memory_*<br>gui_* · subagent_*]
REMOTE[ollie-remote<br>via SSH]
RTOOL[Remote Tools]
LOCAL[Tool Scripts<br>loaded via 9P write<br>· shell, reasoning_think<br>· file_*, lsp_*, memory_*<br>· gui_*, subagent_*]
REMOTE[ollie-remote<br>via SSH · no embedded tools]
end
ACME & EMACS -->|9P| P9
KDE -->|D-Bus| DBUS
ACME & EMACS & KDE --> P9
P9 --> SM
DBUS --> SM
SM --> AG
AG --> ROUTE
AG --> TOOLS
TOOLS --> SB
AG -->|streaming| OLL & OAI & ANT & COP & KIRO
SB --> LOCAL
SB -->|SSH| REMOTE --> RTOOL
TOOLS --> LOCAL
TOOLS -->|SSH| REMOTE
```
`olliesrv` is a single binary. Which surface it exposes is a flag:
@ -252,7 +243,40 @@ GUI frontends subscribe once and react — no timers, no file watches, no busy l
- **System tray** — status indicator and quick actions
- **Standalone GUI** — full-featured Qt6 chat application
## Tool registry: declarative tool registration
## Tool registry: zero built-in tools
**No tools are compiled into the Go binary.** Every tool — including `shell` and `reasoning_think` — is an external executable loaded dynamically through the 9P filesystem. Tool definitions, schemas, and execution logic live entirely in the filesystem.
### Loading tools
Tools are loaded by writing their name to `session/{id}/agent/{aid}/tools`:
```bash
# List currently loaded tools
ollie-9p read session/{id}/agent/{aid}/tools
# Load a tool — becomes a native callable function
echo file_glob | ollie-9p write session/{id}/agent/{aid}/tools
# Global catalog: all discoverable tools on disk
ollie-9p read tools
```
The `tool_load` tool is itself a bash script that does exactly this — it wraps the 9P write in a callable function so the LLM can load tools during a turn.
### Agent autoLoad configuration
Agent configs declare which tools to load at startup via `autoLoad`:
```json
{
"autoLoad": ["file_read", "file_edit", "file_glob", "file_grep", "file_write", "tool_load"]
}
```
Each agent profile (default, copilot, explorer, librarian, navigator, taskmanager, theo) has its own `autoLoad` list. The `allowTools` field provides a secondary security gate.
### Declarative registration
Tools are executables in `~/.config/ollie/tools/`. Each one has a `.meta` sidecar
file declaring its JSON schema, documentation, concurrency class, tier, and
@ -260,19 +284,9 @@ privilege requirements. The registry reads `.meta` files — it never inspects t
executable itself.
This is **declarative tool registration**: write a `.meta` file, drop an
executable anywhere on the system, and the tool is live. No server restart,
executable in `$OLLIE_TOOLS_PATH`, and the tool is live. No server restart,
no config file, no code change.
```bash
# Discover all available tools
cat session/{id}/tools
# Load one into your session — becomes a native callable function
echo file_glob | 9p write session/{id}/tools
# The model sees it as a first-class tool on the next turn.
```
The `.meta` file (`file_glob.meta`):
```json
@ -350,6 +364,7 @@ full variant specification.
| **One-shot LLM** | Write to `/generate`, read back the response |
| **Task routing** | Write to `/route`, read back `backend=X model=Y` |
| **Background jobs** | Detach via `shell({"elevated":true, "detach":true})` |
| **Load tools** | Write tool name to `agent/{id}/tools` via 9P |
| **Custom tools** | Drop an executable + `.meta` in `$OLLIE_TOOLS_PATH` |
| **Privileged tools** | Declare `"sudo": true` in `.meta` — broker handles elevation + credentials |
| **Custom agents** | Write a JSON config in `agents/` |

View File

@ -26,7 +26,6 @@ import (
"sync"
"syscall"
"ollie/tools/builtin"
"ollie/toolsrv"
)
@ -51,7 +50,7 @@ func main() {
binDir := filepath.Join(home, ".config", "ollie", "bin")
os.Setenv("PATH", binDir+":"+os.Getenv("PATH"))
// Build the tool server with builtins and registries.
// Build the tool server with tool registry.
toolReg, _ := toolsrv.NewRegistry()
agentID := fmt.Sprintf("agent-%d", os.Getpid())
@ -59,8 +58,6 @@ func main() {
if *yolo {
opts = append(opts, toolsrv.WithYolo())
}
opts = append(opts, toolsrv.WithStrict())
opts = append(opts, toolsrv.WithBuiltins(builtin.Builtins()))
if toolReg != nil {
opts = append(opts, toolsrv.WithToolRegistry(toolReg, agentID))
}

View File

@ -2,12 +2,12 @@
## Philosophy
ollie's design philosophy is Plan 9 / Acme / Unix: a small substrate that integrates with the surrounding system rather than trying to do everything itself. The Go runtime (`agent.Agent`) defines what an agent *is* — an event loop, a backend connection, a message history, and three built-in primitives (`shell`, tool registry, skill registry). Everything else — orchestration, scheduling, workflows, UIs — is pushed out to external tools, scripts, and frontends.
The key difference from the Emacs model: Emacs extensibility means building capabilities *on top of* its Lisp interpreter and text primitives. Ollie's extensibility means **not** building capabilities into the core at all — instead, the core provides integration surfaces (9P filesystem, shell execution) and lets the surrounding system handle everything else. This is the Acme approach: a lightweight framework that delegates to external programs via a shared filesystem namespace.
The key difference from the Emacs model: Emacs extensibility means building capabilities *on top of* its Lisp interpreter and text primitives. Ollie's extensibility means **not** building capabilities into the core at all — instead, the core provides integration surfaces (9P filesystem, agent loop) and lets the surrounding system handle everything else. This is the Acme approach: a lightweight framework that delegates to external programs via a shared filesystem namespace.
The primary integration philosophy is Plan 9's "everything is a file." `olliesrv` exposes agent state and behaviors as files in a 9P namespace. Any program that can read and write files can drive an agent: shell scripts, editors, web apps, cron, containers.
All clients communicate via 9P exclusively. Desktop notifications use D-Bus directly (org.freedesktop.Notifications) for elevation prompts only — this is the last remaining D-Bus dependency.
Design principles:
- **Small extensible core.** The agent runtime is minimal; capabilities come from composing external scripts.
- **Three built-in primitives.** `shell`, tool registry (`tool_load`/`tool_list`/`tool_active`), and skill registry (`skill_load`/`skill_list`/`skill_active`) are the only tools compiled into the core. Everything else is a script.
- **Zero built-in tools.** No tools are compiled into the Go binary. Every tool — including `shell` and `reasoning_think` — is an external script loaded dynamically via the 9P filesystem. The core is pure coordination: stream LLM → dispatch tool calls → loop.
- **One integration path: 9P filesystem. Streaming via blocking reads. No polling.
- **9P namespace declared via EDSL.** The entire filesystem is a single recursive `FsNodeDecl` tree in `fs/spec.go`, validated and built by `BuildTree()` in `fs/builder.go`. Adding a file means adding a `Leaf()` call — no manual stat/readdir/write wiring.
- **No framework lock-in.** Frontends are decoupled via whichever interface they prefer; the core doesn't know or care.
@ -77,7 +77,7 @@ flowchart TB
subgraph Core["Agent Engine (per session)"]
LOOP["Agent Loop\n(agent/loop.go)"]
SESS["Session State\n(agent/history.go)"]
TRV["toolsrv.Server\n· shell execution\n· tool registry\n· landlock sandbox"]
TRV["toolsrv.Server\n· dynamic tool dispatch\n· landlock sandboxed execution\n· remote execution via SSH"]
end
subgraph Backends["LLM Backends"]
OLLAMA["Ollama"]
@ -96,8 +96,8 @@ flowchart TB
LOOP <--> SESS
LOOP --> TRV
LOOP <--> Backends
TRV --> Shell["sandboxed shell\n(landrun)"]
TRV --> Tools["tool scripts\n(OLLIE_TOOLS_PATH)"]
TRV --> Tools["tool scripts\n(OLLIE_TOOLS_PATH)\n· shell, reasoning_think\n· file_read, file_write\n· lsp_*, memory_*\n· gui_*, subagent_*"]
TRV --> Remote["ollie-remote (SSH)\npure 9P RPC\nno embedded tools"]
```
## Core Library
The core is a single Go module (`ollie`) with no binary. Binaries live in `cmd/`.
@ -106,7 +106,7 @@ The core is a single Go module (`ollie`) with no binary. Binaries live in `cmd/`
|---|---|
| `agent/` | Agent struct, loop, history, compaction, hooks, commands, prompt resolution, state |
| `backend/` | Backend interface + LLM providers (Anthropic, OpenAI, Ollama, Gemini, Copilot, CodeWhisperer) |
| `toolsrv/` | Tool server: sandboxed shell execution, named tool dispatch, skill management, result tiering, remote execution |
| `toolsrv/` | Tool server: dynamic tool dispatch, sandboxed execution, result tiering, remote execution (ollie-remote) |
| `session/` | Session lifecycle, config, persistence |
| `detach/` | Background process management (ring buffer, signal) |
| `elevate/` | Elevation broker (privilege escalation daemon, policy) |
@ -196,6 +196,7 @@ Agent configs declare prompts as a JSON array of shell commands. Each command is
├── help help text
├── models model list (cached)
├── agents agent config list
├── tools global tool catalog (all discoverable tools on disk)
├── ctl root control (invalidate, kill)
├── eventwait global event stream (blocking read)
├── complete request-response: code completion
@ -238,16 +239,16 @@ Agent configs declare prompts as a JSON array of shell commands. Each command is
│ ├── systemprompt rendered system prompt
│ ├── context rendered context window
│ ├── tail exec helper for tailing chat
│ ├── tools tool management (read all, write name to load)
│ ├── toolsrv.loaded currently loaded tools
│ ├── toolsrv.rev revision counter
│ ├── tools tool management (read = list loaded, write name = load)
│ └── proc/{pid} detached process output
```
### 9P Interaction Model
All agent interaction uses the `ollie-9p` client, which auto-discovers the server via `$NAMESPACE` and identifies the caller via `$OLLIE_UNAME`. The 9P filesystem exposes session state, control, and inter-agent communication as files:
All agent interaction uses the `ollie-9p` client, which auto-discovers the server via `$NAMESPACE` and identifies the caller via `$OLLIE_UNAME`. The 9P filesystem exposes session state, control, tool management, and inter-agent communication as files:
- Sessions are directories under `session/`
- Session creation is a two-step process: `session/new` (name only) then `session/{name}/agent/new` (full config)
- Sessions and agents have **immutable UUIDs** (`id` file) and **mutable human-readable names** (`name` file); writing to `name` renames the entry
- **Tool loading is a file write** — writing a tool name to `agent/{id}/tools` loads it for the agent; reading it lists currently loaded tools
- **Global tool catalog** at `/tools` lists all discoverable tools on disk
- Prompts, state, chat history, and config are readable/writable files
- Streaming via blocking reads: `chat`, `statewait`, `eventwait` block the 9P read until data arrives
- Permission enforcement uses 9P user principals

View File

@ -333,7 +333,7 @@ themselves (gopls, clangd, intelephense).
### Repo reorganization
- `contrib/{prompts,scripts,services,tools,skills,agents}` → `data/`
- `contrib/elisp` stays (Emacs frontend contribution)
- `tools/builtin/` — built-in tool handlers (moved from `tools/`)
- `tools/builtin/` — built-in tool handlers (moved from `tools/`, removed in Phase 18)
- `tools/lsp/` — Go LSP implementation (replaced `data/tools/_lib/lsp/`)
### Conditional variants for network transparency
@ -683,3 +683,137 @@ ollie/ ← single Go module
| Single-step session creation | Split into `session/new` + `session/{name}/agent/new` |
| D-Bus-driven KDE frontends | Rewritten on pure 9P via plan9port `9p` binary |
| Plasmoid/tray KDE components | Deleted — not useful enough to maintain |
| Built-in tool handlers (shell, reasoning_think, tool_list, tool_load, tool_active) | Replaced by external scripts loaded via 9P `agent/{id}/tools` write |
| Go-compiled tool registry in `tools/builtin/` | Zero built-in tools — all tools are external scripts with `.meta` sidecars |
| Embedded tool scripts in `ollie-remote` | Remote uses `$OLLIE_TOOLS_PATH` from environment; tools deployed via tarball over SSH |
| SSH binary transfer (gzip+base64+hash-verify) | Replaced by simple `tar | ssh` tarball of `~/.config/ollie/` |
| `toolsrv.loaded` / `toolsrv.rev` 9P files | Removed — `tools` file handles both list and load |
## Phase 18: Zero Built-in Tools (Aug 2–3)
The most radical simplification yet: **zero tools compiled into the Go binary.** Every tool — including `shell` and `reasoning_think` — is now an external script loaded dynamically through the 9P filesystem.
### What changed
**Before**: `tools/builtin/builtins.go` returned four Go-compiled handlers (`shell`, `reasoning_think`, `tool_list`, `tool_load`, `tool_active`). These were hard-linked into every agent's tool definition, consuming LLM context slots even when unused. The tool registry was a two-tier system: built-in handlers (Go code, always available) + script tools (loaded on demand via `tool_load`).
**After**: `tools/builtin/` was removed entirely — `builtins.go` last returned `nil`. All tools are loaded through a single convergent path: writing the tool name to `session/{id}/agent/{aid}/tools`. The `shell` and `reasoning_think` tools are now external scripts (`data/tools/shell`, `data/tools/reasoning_think`) with `.meta` sidecars — identical to `file_read`, `file_grep`, or any other tool.
### Unified tool loading
Tool loading follows a single path shared by both 9P writes and autoLoad initialization:
```
9P write to agent/{id}/tools
│
▼
session.loadTool(name, agent)
│
├── Check disallow list
└── RPC "tool_load" → ollie-remote process
```
The `tool_load` tool itself is a bash script (`data/tools/tool_load`) that writes the tool name to the 9P filesystem — it's a meta-tool that uses the same interface as every other operation.
### Agent config: `autoLoad`
Agent configuration files now declare which tools to load at startup via an `autoLoad` array:
```json
{
"autoLoad": [
"file_read",
"file_edit",
"file_glob",
"file_grep",
"file_write",
"tool_load"
]
}
```
Each agent profile (default, copilot, explorer, librarian, navigator, taskmanager, theo) has its own `autoLoad` list tailored to its domain. The `allowTools` field provides a secondary security gate.
### Tool output format classification
Each tool's `.meta` can declare an `outputFormat` field specifying the source-fence language for its output:
```json
{
"description": "...",
"outputFormat": "markdown"
}
```
The agent loop propagates this to the chat log as the fence language (`[[[tool:file_read]]]\`\`\`markdown`), enabling syntax-highlighted output in the KDE GUI. Tools without `outputFormat` default to plaintext fences.
### Remote execution simplified
`ollie-remote` no longer embeds tool scripts or sandbox config:
- Removed `//go:embed all:tools` — tools come from `$OLLIE_TOOLS_PATH` in the environment
- Removed embedded sandbox `default.yaml` — sandbox config is provisioned alongside tools
- Removed `--tools` flag — uses `~/.config/ollie/tools` by default
### SSH bootstrap simplified
The SSH bootstrap sequence was cut from a complex multi-stage protocol to a single tarball transfer:
| Before | After |
|--------|-------|
| SHA256 hash verification | No hashing |
| gzip + base64 binary encoding | Raw `tar czf` tarball |
| `OLLIE_LOADER_START` / `OLLIE_LOADER_READY` handshake | Single `OLLIE_LISTEN_READY` signal |
| Separate `bootstrap.sh` template (88 lines) | Inline 10-line script |
| Per-binary transfer (ollie-remote + landrun) | Single `tar` of `~/.config/ollie/` |
The simplified bootstrap:
```sh
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/ollie"
mkdir -p "$CACHE_DIR"
tar xzf - -C "$CACHE_DIR" # receives tarball from local
export OLLIE_TOOLS_PATH="$CACHE_DIR/tools"
export PATH="$CACHE_DIR/bin:$PATH"
exec "$CACHE_DIR/bin/ollie-remote" serve --cwd <dir> --listen <sock>
```
### 9P namespace changes
- `agent/{id}/tools` — **read** lists all currently loaded tools (via the session's `toolsConn`), **write** loads a tool by name (via `session.loadTool`)
- Removed `agent/{id}/toolsrv.loaded` — subsumed by `tools` read
- Removed `agent/{id}/toolsrv.rev` — no longer needed
- Added root-level `/tools` — lists all discoverable tools on disk (global catalog)
- Removed `HandlerCtx.ToolReg` — the in-memory Go-level registry is no longer passed through 9P handlers
### Files removed
| File | Status |
|------|--------|
| `tools/builtin/` | Package removed entirely — all tool logic lives in external scripts |
| `tools/builtin/shell.go` | Deleted (now `data/tools/shell` + `data/tools/shell.meta`) |
| `tools/builtin/reasoning.go` | Deleted (now `data/tools/reasoning_think` + `data/tools/reasoning_think.meta`) |
| `tools/builtin/tool.go` | Deleted (`tool_list`/`tool_load`/`tool_active` replaced by 9P filesystem mechanism) |
| `toolsrv/bootstrap.sh` | Deleted (inline bootstrap replaces multi-stage protocol) |
| `cmd/ollie-remote/tools/.gitkeep` | Deleted (no embedded tools) |
### Lines of code
- **−530 lines** from the Go core (removed built-in handlers, embedded tools, bootstrap script)
- **+82 lines** for external tool scripts + `.meta` sidecars (`shell`, `reasoning_think`, `tool_load`)
- **Net**: tools system is smaller, simpler, and more uniform
### Why it matters
The zero-tools transition is the culmination of the architectural trajectory that began with the tool registry in Phase 10: **every facility is a file; nothing is hard-coded.**
1. **Uniformity** — There is no distinction between "core" tools and "script" tools. `shell`, `reasoning_think`, `file_read`, `file_write` — all loaded the same way, all have `.meta` sidecars, all called via the same RPC mechanism.
2. **Minimal core** — The Go binary no longer contains any tool logic. The agent loop is pure coordination: stream LLM → parse tool calls → dispatch to remote process → loop. Tool definitions, schemas, and execution are entirely external.
3. **Agent-choosable tools** — Each agent profile declares exactly which tools it needs. No agent pays the context-window cost of `shell` and `reasoning_think` unless its config asks for them.
4. **9P as the sole control plane** — Tool loading is now a file write. The same mechanism (`ollie-9p write ...`) that submits prompts and reads state also manages tool loading. No special RPC, no separate tool-manager protocol.
5. **Simpler remote deployment** — No embedded files, no binary transfer protocol, no hash verification. Provisioning a remote host is `tar czf ~/.config/ollie | ssh host tar xzf -`.
The result: the Go core is a 9P file server and an agent loop, nothing more. All behavior — every tool, skill, prompt, and configuration — lives in data files on disk. This is the Plan 9 ideal: a small, fixed kernel; all policy and capability in the filesystem.

View File

@ -26,7 +26,7 @@ build-kf5: core ninep ollie-remote acme kde-kf5
core:
@echo === Building ollie-core ===
go build ./agent/... ./backend/... ./detach/... ./elevate/... ./env/... ./fs/... ./log/... ./mount/... ./paths/... ./prompts/... ./sandbox/... ./session/... ./skills/... ./tools/builtin/... ./tools/lsp/... ./toolsrv/...
go build ./agent/... ./backend/... ./detach/... ./elevate/... ./env/... ./fs/... ./log/... ./mount/... ./paths/... ./prompts/... ./sandbox/... ./session/... ./skills/... ./tools/lsp/... ./toolsrv/...
ninep:
@echo === Building ollie-9p ===
@ -221,7 +221,7 @@ test: test-core test-9p
test-core:
@echo === Testing ollie-core ===
go test ./agent/... ./backend/... ./detach/... ./elevate/... ./env/... ./log/... ./paths/... ./sandbox/... ./session/... ./skills/... ./tools/builtin/... ./toolsrv/... ./prompts/...
go test ./agent/... ./backend/... ./detach/... ./elevate/... ./env/... ./log/... ./paths/... ./sandbox/... ./session/... ./skills/... ./toolsrv/... ./prompts/...
test-9p:
@echo === Testing ollie-9p ===

View File

@ -1,12 +0,0 @@
// Package tools provides the built-in tool handlers for the toolsrv server.
// Each file in this package is one tool. Builtins() is the single registry.
package builtin
import "ollie/toolsrv"
// Builtins returns the default set of built-in tool handlers.
// Returns nil — there are zero built-in tools. All tools are loaded
// dynamically via the 9P agent/{id}/tools write mechanism.
func Builtins() map[string]toolsrv.Handler {
return nil
}

View File

@ -42,13 +42,9 @@ type Server struct {
envMu sync.RWMutex
envExtra map[string]string
// Hooks for lifecycle events (harnesses like 9P can inject mount logic here)
OnPreDispatch func()
OnClose func()
OnEnvSet func(key, value string)
// Strict rejects inline {code} steps; only {tool} steps are allowed.
Strict bool
// Hooks for lifecycle events (harnesses like 9P can inject mount logic here)
OnClose func()
OnEnvSet func(key, value string)
// Yolo skips the landrun sandbox for all execution.
Yolo bool
@ -57,9 +53,6 @@ type Server struct {
// Empty means all are allowed.
allowTools map[string]bool
// builtins maps tool names to their handler functions.
builtins map[string]Handler
toolRegistry *Registry
sessionID string
@ -88,15 +81,9 @@ type Server struct {
// Option configures a Server.
type Option func(*Server)
// WithStrict rejects inline {code} steps; only {tool} steps are allowed.
func WithStrict() Option { return func(s *Server) { s.Strict = true } }
// WithYolo skips the landrun sandbox.
func WithYolo() Option { return func(s *Server) { s.Yolo = true } }
// WithOnPreDispatch registers a hook called before each Dispatch.
func WithOnPreDispatch(fn func()) Option { return func(s *Server) { s.OnPreDispatch = fn } }
// WithOnClose registers a hook called during Close.
func WithOnClose(fn func()) Option { return func(s *Server) { s.OnClose = fn } }
@ -130,11 +117,6 @@ func (e *Server) AllowTools() []string {
return out
}
// WithBuiltins registers the built-in tool handlers.
func WithBuiltins(m map[string]Handler) Option {
return func(s *Server) { s.builtins = m }
}
// WithToolRegistry attaches a tool registry and session ID to the Server.
func WithToolRegistry(r *Registry, sessionID string) Option {
return func(s *Server) {
@ -169,30 +151,15 @@ func (e *Server) ListTools() ([]ToolInfo, error) {
return all, nil
}
// CallTool implements Server.
// CallTool implements Runner.
// With zero built-in tools, all tools must be loaded in the registry first.
func (e *Server) CallTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error) {
if e.toolRegistry != nil && e.sessionID != "" {
if _, promoted := e.toolRegistry.Lookup(e.sessionID, tool); promoted {
return e.callPromotedTool(ctx, tool, args)
}
}
result, err := e.Dispatch(ctx, tool, args)
if err != nil {
return json.Marshal(map[string]any{
"isError": true,
"content": []map[string]string{{"type": "text", "text": err.Error()}},
})
}
// If the tool output is already structured content-block JSON, pass it through.
var structured map[string]json.RawMessage
if json.Unmarshal([]byte(result), &structured) == nil {
if _, ok := structured["content"]; ok {
return []byte(result), nil
}
}
return json.Marshal(map[string]any{
"content": []map[string]string{{"type": "text", "text": result}},
})
return nil, fmt.Errorf("unknown tool: %s (not loaded)", tool)
}
// New creates a new Server with the given working directory.
@ -316,18 +283,6 @@ func (e *Server) Close() {
}
}
// Dispatch routes tool calls to the appropriate handler.
// Dispatch routes tool calls to the appropriate built-in handler.
func (e *Server) Dispatch(ctx context.Context, name string, args json.RawMessage) (string, error) {
if e.OnPreDispatch != nil {
e.OnPreDispatch()
}
if h, ok := e.builtins[name]; ok {
return h(ctx, e, args)
}
return "", fmt.Errorf("unknown tool: %s", name)
}
// Detach signals the currently running process to be detached from the agent.
// The process continues running; its output is captured in a ring buffer.
// Returns false if no process is currently running.

View File

@ -53,6 +53,3 @@ type TierClassifier interface {
type OutputFormatClassifier interface {
OutputFormat(name string) string
}
// Handler is the signature for built-in tool handlers.
type Handler func(ctx context.Context, srv *Server, args json.RawMessage) (string, error)