docs: clarify tool usage in system prompt, add cancellation flow doc

- Explicitly tell model not to invoke tool scripts via filesystem path
- Add concrete examples of native tool calls vs shell
- Document context cancellation flow from interrupt to process kill
This commit is contained in:
Levi Neely 2026-08-12 12:20:34 +02:00
parent aedfdec564
commit 9057d36f05
2 changed files with 170 additions and 2 deletions

View File

@ -26,9 +26,16 @@ Proactively load what you need — don't wait to be told.
## Tools
Tools are loaded at startup. Additional tools can be loaded at runtime via the `tool_load` tool. Once loaded, a tool is a first-class function — call it directly by name. Do not invoke tools through `shell`.
Tools are loaded at startup. Additional tools can be loaded at runtime via the `tool_load` tool. Once loaded, a tool is a first-class function — call it directly by name.
To see all available tools (loaded and unloaded): `ollie-9p read tools`
**Important**: Use native tool calls, not shell. For example:
- To read a file: call `file_read` directly, NOT `cat` or `shell` with any command
- To search: call `file_grep` directly, NOT `grep` via shell
- To edit: call `file_edit` directly, NOT `sed` via shell
The tool scripts in `~/.config/ollie/tools/` are already exposed as callable functions. Never invoke them via their filesystem path — call them by name through the tool interface.
Shell is for system commands that have no native tool equivalent: builds, git, package managers, interpreters, etc.
## Parallel Calls

View File

@ -0,0 +1,161 @@
# Context Cancellation Flow
This document traces context cancellation from user interrupt to tool process termination.
## Overview
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ OLLIESRV │
│ │
│ User sends "stop" to agent ctl │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Agent.Interrupt │ Loads currentAction (actionHandle) │
│ │ (cause) │ Calls h.cancel(cause) │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ actCtx (context.WithCancelCause) │ Created in Agent.Run() │
│ │ │ at turn start (turn.go:95) │
│ └────────┬────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ TurnCtx (embeds │ Passed to loop.go, execOne(), etc. │
│ │ context.Context)│ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ execOne() checks ctx.Err() │ loop.go:540 │
│ │ If cancelled → returns immediately │ │
│ │ with status:"cancelled" │ │
│ └────────┬────────────────────────────────┘ │
│ │ │
│ │ If not cancelled, calls rt.Exec(ctx, name, args) │
│ │ which wraps srv.CallTool(ctx, ...) (runtime.go:181) │
│ ▼ │
└───────────┼──────────────────────────────────────────────────────────────────┘
│
│ UNIX SOCKET (9P PROTOCOL)
│
┌───────────┼──────────────────────────────────────────────────────────────────┐
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ toolsrv/client9p.go: CallTool() │ │
│ │ │ │
│ │ 1. Opens "proc/new" file (9P fid) │ │
│ │ │ │
│ │ 2. Starts goroutine watching ctx.Done(): │ │
│ │ go func() { │ │
│ │ select { │ │
│ │ case <-ctx.Done(): │ │
│ │ fid.Close() // ← Force close to unblock read │ │
│ │ case <-done: │ │
│ │ } │ │
│ │ }() │ │
│ │ │ │
│ │ 3. Writes payload to fid (Twrite) │ │
│ │ 4. Reads result from fid (Tread) — BLOCKS │ │
│ │ 5. If read fails AND ctx.Err() != nil → return ctx.Err() │ │
│ └────────┬────────────────────────────────────────────────────────┘ │
│ │ │
│ │ fid.Close() sends Tclunk to toolsrv │
│ ▼ │
└───────────┼──────────────────────────────────────────────────────────────────┘
│
┌───────────┼──────────────────────────────────────────────────────────────────┐
│ │ TOOLSRV │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ p9.go: handleClunk() receives Tclunk │ │
│ │ │ │
│ │ 1. Removes fid from map │ │
│ │ 2. Calls f.entry.Close() (p9.go:550) │ │
│ └────────┬────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ virtfs/builder.go: closeFn() for Request mode │ │
│ │ │ │
│ │ closeFn := func() error { │ │
│ │ if reqCancel != nil { │ │
│ │ reqCancel() // ← Cancels the request context │ │
│ │ } │ │
│ │ return nil │ │
│ │ } │ │
│ │ │ │
│ │ This cancels reqCtx created in writeFn (builder.go:274) │ │
│ └────────┬────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ server/proc.go: HandleProcNew() receives cancelled context │ │
│ │ │ │
│ │ The Request handler was called with reqCtx: │ │
│ │ virtfs.Request(func(ctx context.Context, data []byte) ...) │ │
│ │ │ │
│ │ When reqCancel() is called, ctx.Done() becomes readable │ │
│ └────────┬────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ exec/exec.go: ExecuteTool() sees context cancellation │ │
│ │ │ │
│ │ The exec code should check ctx.Done() and: │ │
│ │ 1. Send SIGKILL to the child process │ │
│ │ 2. Return error to caller │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
```
## Key Files
| Location | Responsibility |
|----------|----------------|
| `cmd/olliesrv/internal/agent/agent.go:308` | `Interrupt()` calls `h.cancel(cause)` |
| `cmd/olliesrv/internal/agent/turn.go:95` | Creates `actCtx` with `WithCancelCause` |
| `cmd/olliesrv/internal/agent/loop.go:540` | `execOne()` checks `ctx.Err()` early |
| `cmd/olliesrv/internal/agent/runtime.go:181` | `rt.Exec` calls `srv.CallTool(ctx, ...)` |
| `toolsrv/client9p.go:140-155` | `CallTool()` goroutine closes fid on ctx.Done() |
| `cmd/toolsrv/p9.go:549-551` | `handleClunk()` calls `f.entry.Close()` |
| `virtfs/builder.go:282-286` | `closeFn` cancels `reqCtx` |
| `cmd/toolsrv/internal/exec/exec.go` | Should kill process on context cancellation |
## Sequence Diagram
```
User Agent CallTool 9P Server virtfs exec
│ │ │ │ │ │
│──"stop"────▶│ │ │ │ │
│ │ │ │ │ │
│ │──cancel()─────▶│ │ │ │
│ │ │ │ │ │
│ │ │──fid.Close()──▶│ │ │
│ │ │ (Tclunk) │ │ │
│ │ │ │ │ │
│ │ │ │──Close()─────▶│ │
│ │ │ │ │ │
│ │ │ │ │──reqCancel()─▶│
│ │ │ │ │ │
│ │ │ │ │ │──SIGKILL──▶[process]
│ │ │ │ │ │
│ │◀───ctx.Err()───│ │ │ │
│ │ │ │ │ │
│◀──cancelled─│ │ │ │ │
```
## Potential Issues
1. **Race condition**: The Tclunk might arrive after the tool has already completed
- This is benign: cancelling a completed context is a no-op
2. **Long cleanup**: If the tool doesn't respond to SIGKILL quickly, the cancellation
may appear to hang briefly
3. **Background processes**: Background tools (via `proc/new.bg`) are NOT cancelled
by this mechanism - they run until completion or explicit kill